Repository: VolkovLabs/volkovlabs-dynamictext-panel (ts)

Issue:
Migration script doesn't take exising context.* usage into account 
When updating the panel to 5.0.0, the migration script does not take existing context.* usage into account, like in the 'Before content rendering', the following script:

```context.handlebars.registerHelper("containerId", () => `container-${context.panelData.request.panelId}`);```

is migrated to

```context.handlebars.registerHelper("containerId", () => `container-${context.context.panelData.request.panelId}`);```

therefore all panels will break after the migration. Please update the migration script to ignore existing usage of the new context.* structure.

Pull request:
Update helpers migration
Resolves #329 

Patch:
diff --git a/docker-compose.yml b/docker-compose.yml
index c8a4809..99ca5d3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,6 +1,5 @@
 services:
   grafana:
-    container_name: grafana
     image: ghcr.io/volkovlabs/app:latest
     entrypoint: /run.sh
     ports:
@@ -16,7 +15,6 @@ services:
       - dev
 
   grafana-main:
-    container_name: grafana-main
     image: grafana/grafana:main
     ports:
       - 3000:3000/tcp
@@ -31,7 +29,6 @@ services:
       - main
 
   test:
-    container_name: test-e2e
     build:
       context: .
       dockerfile: test/Dockerfile
diff --git a/src/migration.ts b/src/migration.ts
index 030a6e4..fc21962 100644
--- a/src/migration.ts
+++ b/src/migration.ts
@@ -20,44 +20,54 @@ interface OutdatedPanelOptions {
  */
 const normalizeHelpersOption = (code: string): string => {
   const search =
-    /(handlebars|panelData|dataFrame\.|data.|getLocale\(|timezone|timeRange|eventBus|locationService|replaceVariables\()/gm;
-  return code.replace(search, (value) => {
-    switch (value) {
-      case 'data.': {
-        return 'context.data.';
-      }
-      case 'handlebars': {
-        return 'context.handlebars';
-      }
-      case 'dataFrame.': {
-        return 'context.dataFrame.';
-      }
-      case 'panelData': {
-        return 'context.panelData';
-      }
-      case 'getLocale(': {
-        return 'context.grafana.getLocale(';
-      }
-      case 'timezone': {
-        return 'context.grafana.timezone';
-      }
-      case 'timeRange': {
-        return 'context.grafana.timeRange';
-      }
-      case 'eventBus': {
-        return 'context.grafana.eventBus';
-      }
-      case 'locationService': {
-        return 'context.grafana.locationService';
-      }
-      case 'replaceVariables(': {
-        return 'context.grafana.replaceVariables(';
-      }
-      default: {
-        return value;
-      }
-    }
-  });
+    /^(?!.*context\.)(?:.*)(handlebars|panelData|dataFrame\.|data\.|getLocale\(|timeZone|timeRange|eventBus|locationService|replaceVariables\()/gm;
+
+  return code
+    .split(' ')
+    .map((part) => {
+      return part.replace(search, (value, ...args) => {
+        const searchTerm = args[0] || value;
+
+        return value.replace(searchTerm, (valueToReplace) => {
+          switch (valueToReplace) {
+            case 'data.': {
+              return 'context.data.';
+            }
+            case 'handlebars': {
+              return 'context.handlebars';
+            }
+            case 'dataFrame.': {
+              return 'context.dataFrame.';
+            }
+            case 'panelData': {
+              return 'context.panelData';
+            }
+            case 'getLocale(': {
+              return 'context.grafana.getLocale(';
+            }
+            case 'timeZone': {
+              return 'context.grafana.timeZone';
+            }
+            case 'timeRange': {
+              return 'context.grafana.timeRange';
+            }
+            case 'eventBus': {
+              return 'context.grafana.eventBus';
+            }
+            case 'locationService': {
+              return 'context.grafana.locationService';
+            }
+            case 'replaceVariables(': {
+              return 'context.grafana.replaceVariables(';
+            }
+            default: {
+              return value;
+            }
+          }
+        });
+      });
+    })
+    .join(' ');
 };
 
 /**


Test patch:
diff --git a/src/migration.test.ts b/src/migration.test.ts
index b35c022..febf1b8 100644
--- a/src/migration.test.ts
+++ b/src/migration.test.ts
@@ -35,19 +35,23 @@ describe('Migration', () => {
           initial: `
           const data = [];
           data.series.forEach();
+          context.data.series.forEach();
           `,
           expected: `
           const data = [];
           context.data.series.forEach();
+          context.data.series.forEach();
           `,
         },
         {
           name: 'handlebars',
           initial: `
           const helpers = handlebars.helpers;
+          const helpers2 = context.handlebars.helpers;
           `,
           expected: `
           const helpers = context.handlebars.helpers;
+          const helpers2 = context.handlebars.helpers;
           `,
         },
         {
@@ -62,19 +66,23 @@ describe('Migration', () => {
         {
           name: 'dataFrame',
           initial: `
-          const id = dataFrame.refId
+          const id = dataFrame.refId;
+          if (dataFrame.refId) {}
+          if (context.dataFrame.refId) {}
           `,
           expected: `
-          const id = context.dataFrame.refId
+          const id = context.dataFrame.refId;
+          if (context.dataFrame.refId) {}
+          if (context.dataFrame.refId) {}
           `,
         },
         {
-          name: 'timezone',
+          name: 'timeZone',
           initial: `
-          const zone = timezone
+          const zone = timeZone;
           `,
           expected: `
-          const zone = context.grafana.timezone
+          const zone = context.grafana.timeZone;
           `,
         },
         {