refactor(workflows): migrate to the new DI architecture

Bug: twpowertools:226
Change-Id: I8700cfd66883a667fc6ae0dffe9deb96f8d8cc80
diff --git a/src/common/architecture/dependenciesProvider/DependenciesProvider.ts b/src/common/architecture/dependenciesProvider/DependenciesProvider.ts
index 8bbe0b8..02e25ce 100644
--- a/src/common/architecture/dependenciesProvider/DependenciesProvider.ts
+++ b/src/common/architecture/dependenciesProvider/DependenciesProvider.ts
@@ -2,7 +2,6 @@
 import AutoRefresh from '../../../features/autoRefresh/core/autoRefresh';
 import OptionsProviderAdapter from '../../../infrastructure/services/options/OptionsProvider.adapter';
 import WorkflowsImport from '../../../features/workflows/core/communityConsole/import';
-import Workflows from '../../../features/workflows/core/communityConsole/workflows';
 import StartupDataStorageAdapter from '../../../infrastructure/services/communityConsole/StartupDataStorage.adapter';
 import ReportDialogColorThemeFix from '../../../features/ccDarkTheme/core/logic/reportDialog';
 
@@ -12,7 +11,6 @@
 export const ReportDialogColorThemeFixDependency =
   'report-dialog-color-theme-fix';
 export const StartupDataStorageDependency = 'startupDataStorage';
-export const WorkflowsDependency = 'workflows';
 export const WorkflowsImportDependency = 'workflowsImport';
 export const DependenciesToClass = {
   [AutoRefreshDependency]: AutoRefresh,
@@ -20,7 +18,6 @@
   [OptionsProviderDependency]: OptionsProviderAdapter,
   [ReportDialogColorThemeFixDependency]: ReportDialogColorThemeFix,
   [StartupDataStorageDependency]: StartupDataStorageAdapter,
-  [WorkflowsDependency]: Workflows,
   [WorkflowsImportDependency]: WorkflowsImport,
 };
 
diff --git a/src/entryPoints/communityConsole/contentScripts/main.ts b/src/entryPoints/communityConsole/contentScripts/main.ts
index bb7fe6a..9c70743 100644
--- a/src/entryPoints/communityConsole/contentScripts/main.ts
+++ b/src/entryPoints/communityConsole/contentScripts/main.ts
@@ -4,6 +4,7 @@
 import DependenciesProviderSingleton, {
   AutoRefreshDependency,
   OptionsProviderDependency,
+  WorkflowsImportDependency,
 } from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
 import { Context } from '../../../common/architecture/entrypoint/Context';
 import {
@@ -30,6 +31,10 @@
 import CCInfiniteScrollSetUpHandler from '../../../features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollSetUp.handler';
 import CCInfiniteScrollLoadMoreBarHandler from '../../../features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollLoadMoreBar.handler';
 import CCInfiniteScrollLoadMoreBtnHandler from '../../../features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollLoadMoreBtn.handler';
+import WorkflowsThreadListActionBarHandler from '../../../features/workflows/presentation/nodeWatcherHandlers/threadListActionBar.handler';
+import WorkflowsImportCRTagsHandler from '../../../features/workflows/presentation/nodeWatcherHandlers/crTags.handler';
+import Workflows from '../../../features/workflows/core/communityConsole/workflows';
+import WorkflowsImportStylesheetScript from '../../../features/workflows/presentation/scripts/importStylesheet';
 
 const scriptRunner = createScriptRunner();
 scriptRunner.run();
@@ -40,6 +45,9 @@
   const optionsProvider = dependenciesProvider.getDependency(
     OptionsProviderDependency,
   );
+  const workflowsImport = dependenciesProvider.getDependency(
+    WorkflowsImportDependency,
+  );
 
   const ccInfiniteScroll = new CCInfiniteScroll();
 
@@ -88,11 +96,20 @@
               'ccInfiniteScrollLoadMoreBtn',
               new CCInfiniteScrollLoadMoreBtnHandler(ccInfiniteScroll),
             ],
+            [
+              'workflowsImportCRTags',
+              new WorkflowsImportCRTagsHandler(workflowsImport),
+            ],
+            [
+              'workflowsThreadListActionBar',
+              new WorkflowsThreadListActionBarHandler(new Workflows()),
+            ],
           ]),
         ),
 
         // Individual feature scripts
         new AutoRefreshStylesScript(),
+        new WorkflowsImportStylesheetScript(),
 
         // Non-DI scripts (legacy, should be migrated to use a DI approach)
         ...new Features().getScripts(context),
diff --git a/src/entryPoints/communityConsole/contentScripts/start.ts b/src/entryPoints/communityConsole/contentScripts/start.ts
index c408b9f..73d2230 100644
--- a/src/entryPoints/communityConsole/contentScripts/start.ts
+++ b/src/entryPoints/communityConsole/contentScripts/start.ts
@@ -5,6 +5,7 @@
   AutoRefreshDependency,
   OptionsProviderDependency,
   StartupDataStorageDependency,
+  WorkflowsImportDependency,
 } from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
 import { Context } from '../../../common/architecture/entrypoint/Context';
 import {
@@ -22,6 +23,7 @@
 import { SortedScriptsProviderAdapter } from '../../../infrastructure/presentation/scripts/SortedScriptsProvider.adapter';
 import StandaloneScripts from '../../../scripts/Scripts';
 import LoadDraftsSetupScript from '../../../features/loadDrafts/presentation/scripts/setup.script';
+import WorkflowsImportSetUpScript from '../../../features/workflows/presentation/scripts/importSetUp.script';
 
 const scriptRunner = createScriptRunner();
 scriptRunner.run();
@@ -35,6 +37,9 @@
   const startupDataStorage = dependenciesProvider.getDependency(
     StartupDataStorageDependency,
   );
+  const workflowsImport = dependenciesProvider.getDependency(
+    WorkflowsImportDependency,
+  );
 
   const context: Context = {
     page: ScriptPage.CommunityConsole,
@@ -51,6 +56,7 @@
         new CCDarkThemeInjectForcedDarkTheme(),
         new InteropThreadPageSetupScript(),
         new LoadDraftsSetupScript(optionsProvider, startupDataStorage),
+        new WorkflowsImportSetUpScript(workflowsImport),
 
         // Non-DI scripts (legacy, should be migrated to use a DI approach)
         ...new Features().getScripts(context),
diff --git a/src/features/Features.ts b/src/features/Features.ts
index 7f2a061..b79d4fa 100644
--- a/src/features/Features.ts
+++ b/src/features/Features.ts
@@ -1,14 +1,12 @@
 import Feature from '../common/architecture/features/Feature';
 import ScriptFilterListProvider from '../common/architecture/scripts/ScriptFilterListProvider';
 import ExtraInfoFeature from './extraInfo/extraInfo.feature';
-import WorkflowsFeature from './workflows/workflows.feature';
 
 export type ConcreteFeatureClass = { new (): Feature };
 
 export default class Features extends ScriptFilterListProvider {
   private features: ConcreteFeatureClass[] = [
     ExtraInfoFeature,
-    WorkflowsFeature,
   ];
   private initializedFeatures: Feature[];
 
diff --git a/src/features/workflows/core/communityConsole/import.js b/src/features/workflows/core/communityConsole/import.js
index 09ffc0a..a527151 100644
--- a/src/features/workflows/core/communityConsole/import.js
+++ b/src/features/workflows/core/communityConsole/import.js
@@ -6,13 +6,19 @@
 
 const kListCannedResponsesResponse = 'TWPT_ListCannedResponsesResponse';
 
-const kImportParam = 'TWPTImportToWorkflow';
+export const kImportParam = 'TWPTImportToWorkflow';
 const kSelectedIdParam = 'TWPTSelectedId';
 
 // Class which is used to inject a "select" button in the CRs list when loading
 // the canned response list for this purpose from the workflows manager.
 export default class WorkflowsImport {
   constructor() {
+    this.isSetUp = false;
+  }
+
+  setUp() {
+    if (this.isSetUp) return;
+
     // Only set this class up if the Community Console was opened with the
     // purpose of importing CRs to the workflow manager.
     const searchParams = new URLSearchParams(document.location.search);
@@ -27,34 +33,18 @@
       duplicateNames: new Set(),
     };
 
-    this.setUpHandler();
-    this.addCustomStyles();
+    this._setUpHandler();
   }
 
-  setUpHandler() {
-    window.addEventListener(kListCannedResponsesResponse, e => {
-      if (e.detail.id < this.lastCRsList.id) return;
-
-      // Look if there are duplicate names
-      const crs = e.detail.body?.['1'] ?? [];
-      const names = crs.map(cr => cr?.['7']).slice().sort();
-      let duplicateNames = new Set();
-      for (let i = 1; i < names.length; i++)
-        if (names[i - 1] == names[i]) duplicateNames.add(names[i]);
-
-      this.lastCRsList = {
-        body: e.detail.body,
-        id: e.detail.id,
-        duplicateNames,
-      };
+  addButtonIfApplicable(tags) {
+    this.setUp();
+    if (!this.isSetUp) return;
+    isOptionEnabled('workflows').then(isEnabled => {
+      if (isEnabled) this._addButton(tags);
     });
   }
 
-  addCustomStyles() {
-    injectStylesheet(chrome.runtime.getURL('css/workflow_import.css'));
-  }
-
-  addButton(tags) {
+  _addButton(tags) {
     const cr = recursiveParentElement(tags, 'EC-CANNED-RESPONSE-ROW');
     if (!cr) return;
 
@@ -101,10 +91,22 @@
     });
   }
 
-  addButtonIfApplicable(tags) {
-    if (!this.isSetUp) return;
-    isOptionEnabled('workflows').then(isEnabled => {
-      if (isEnabled) this.addButton(tags);
+  _setUpHandler() {
+    window.addEventListener(kListCannedResponsesResponse, e => {
+      if (e.detail.id < this.lastCRsList.id) return;
+
+      // Look if there are duplicate names
+      const crs = e.detail.body?.['1'] ?? [];
+      const names = crs.map(cr => cr?.['7']).slice().sort();
+      let duplicateNames = new Set();
+      for (let i = 1; i < names.length; i++)
+        if (names[i - 1] == names[i]) duplicateNames.add(names[i]);
+
+      this.lastCRsList = {
+        body: e.detail.body,
+        id: e.detail.id,
+        duplicateNames,
+      };
     });
   }
 }
diff --git a/src/features/workflows/core/communityConsole/workflows.js b/src/features/workflows/core/communityConsole/workflows.js
index 1110923..9162795 100644
--- a/src/features/workflows/core/communityConsole/workflows.js
+++ b/src/features/workflows/core/communityConsole/workflows.js
@@ -6,8 +6,13 @@
 
 export default class Workflows {
   constructor() {
+    this.isSetUp = false;
     this.menu = null;
     this.workflows = null;
+  }
+
+  setUp() {
+    if (this.isSetUp) return;
 
     // Always keep the workflows list updated
     WorkflowsStorage.watch(workflows => {
@@ -21,6 +26,8 @@
         message: 'openWorkflowsManager',
       });
     });
+
+    this.isSetup = true;
   }
 
   _emitWorkflowsUpdateEvent() {
@@ -34,6 +41,8 @@
   }
 
   addThreadListBtnIfEnabled(readToggle) {
+    this.setUp();
+
     isOptionEnabled('workflows').then(isEnabled => {
       if (isEnabled) {
         this.menu = document.createElement('twpt-workflows-inject');
diff --git a/src/features/workflows/nodeWatcherHandlers/crTags.handler.ts b/src/features/workflows/nodeWatcherHandlers/crTags.handler.ts
deleted file mode 100644
index 040c5ed..0000000
--- a/src/features/workflows/nodeWatcherHandlers/crTags.handler.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import CssSelectorNodeWatcherScriptHandler from '../../../common/architecture/scripts/nodeWatcher/handlers/CssSelectorNodeWatcherScriptHandler';
-import { NodeMutation } from '../../../presentation/nodeWatcher/NodeWatcherHandler';
-import { WorkflowsNodeWatcherDependencies } from '../scripts/nodeWatcher.script';
-
-/**
- * Injects the button to import a canned response next to each CR.
- */
-export default class WorkflowsImportCRTagsHandler extends CssSelectorNodeWatcherScriptHandler<WorkflowsNodeWatcherDependencies> {
-  cssSelector = 'ec-canned-response-row .tags';
-
-  onMutatedNode(mutation: NodeMutation) {
-    this.options.workflowsImport.addButtonIfApplicable(mutation.node);
-  }
-}
diff --git a/src/features/workflows/nodeWatcherHandlers/threadListActionBar.handler.ts b/src/features/workflows/nodeWatcherHandlers/threadListActionBar.handler.ts
deleted file mode 100644
index ab23f11..0000000
--- a/src/features/workflows/nodeWatcherHandlers/threadListActionBar.handler.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { NodeWatcherScriptHandler } from '../../../common/architecture/scripts/nodeWatcher/handlers/NodeWatcherScriptHandler';
-import { NodeMutation } from '../../../presentation/nodeWatcher/NodeWatcherHandler';
-import { WorkflowsNodeWatcherDependencies } from '../scripts/nodeWatcher.script';
-
-/**
- * Injects the workflows menu in the thread list.
- */
-export default class WorkflowsThreadListActionBarHandler extends NodeWatcherScriptHandler<WorkflowsNodeWatcherDependencies> {
-  initialDiscoverySelector =
-    ':is(ec-bulk-actions material-button[debugid="mark-read-button"],' +
-    'ec-bulk-actions material-button[debugid="mark-unread-button"])';
-
-  nodeFilter(mutation: NodeMutation) {
-    return this.options.workflows.shouldAddThreadListBtn(mutation.node);
-  }
-
-  onMutatedNode(mutation: NodeMutation) {
-    this.options.workflows.addThreadListBtnIfEnabled(mutation.node);
-  }
-}
diff --git a/src/features/workflows/presentation/nodeWatcherHandlers/crTags.handler.ts b/src/features/workflows/presentation/nodeWatcherHandlers/crTags.handler.ts
new file mode 100644
index 0000000..f4001c9
--- /dev/null
+++ b/src/features/workflows/presentation/nodeWatcherHandlers/crTags.handler.ts
@@ -0,0 +1,18 @@
+import CssSelectorNodeWatcherHandler from '../../../../infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter';
+import { NodeMutation } from '../../../../presentation/nodeWatcher/NodeWatcherHandler';
+import WorkflowsImport from '../../core/communityConsole/import';
+
+/**
+ * Injects the button to import a canned response next to each CR.
+ */
+export default class WorkflowsImportCRTagsHandler extends CssSelectorNodeWatcherHandler {
+  cssSelector = 'ec-canned-response-row .tags';
+
+  constructor(private workflowsImport: WorkflowsImport) {
+    super();
+  }
+
+  onMutatedNode(mutation: NodeMutation) {
+    this.workflowsImport.addButtonIfApplicable(mutation.node);
+  }
+}
diff --git a/src/features/workflows/presentation/nodeWatcherHandlers/threadListActionBar.handler.ts b/src/features/workflows/presentation/nodeWatcherHandlers/threadListActionBar.handler.ts
new file mode 100644
index 0000000..d100b9d
--- /dev/null
+++ b/src/features/workflows/presentation/nodeWatcherHandlers/threadListActionBar.handler.ts
@@ -0,0 +1,26 @@
+import {
+  NodeMutation,
+  NodeWatcherHandler,
+} from '../../../../presentation/nodeWatcher/NodeWatcherHandler';
+import Workflows from '../../core/communityConsole/workflows';
+
+/**
+ * Injects the workflows menu in the thread list.
+ */
+export default class WorkflowsThreadListActionBarHandler
+  implements NodeWatcherHandler
+{
+  initialDiscoverySelector =
+    ':is(ec-bulk-actions material-button[debugid="mark-read-button"],' +
+    'ec-bulk-actions material-button[debugid="mark-unread-button"])';
+
+  constructor(private workflows: Workflows) {}
+
+  nodeFilter(mutation: NodeMutation) {
+    return this.workflows.shouldAddThreadListBtn(mutation.node);
+  }
+
+  onMutatedNode(mutation: NodeMutation) {
+    this.workflows.addThreadListBtnIfEnabled(mutation.node);
+  }
+}
diff --git a/src/features/workflows/presentation/scripts/importSetUp.script.ts b/src/features/workflows/presentation/scripts/importSetUp.script.ts
new file mode 100644
index 0000000..73edf50
--- /dev/null
+++ b/src/features/workflows/presentation/scripts/importSetUp.script.ts
@@ -0,0 +1,17 @@
+import Script from '../../../../common/architecture/scripts/Script';
+import WorkflowsImport from '../../core/communityConsole/import';
+
+export default class WorkflowsImportSetUpScript extends Script {
+  priority = 102;
+  page: never;
+  environment: never;
+  runPhase: never;
+
+  constructor(private workflowsImport: WorkflowsImport) {
+    super();
+  }
+
+  execute() {
+    this.workflowsImport.setUp();
+  }
+}
diff --git a/src/features/workflows/presentation/scripts/importStylesheet.ts b/src/features/workflows/presentation/scripts/importStylesheet.ts
new file mode 100644
index 0000000..1b0a443
--- /dev/null
+++ b/src/features/workflows/presentation/scripts/importStylesheet.ts
@@ -0,0 +1,16 @@
+import Script from '../../../../common/architecture/scripts/Script';
+import { injectStylesheet } from '../../../../common/contentScriptsUtils';
+import { kImportParam } from '../../core/communityConsole/import';
+
+export default class WorkflowsImportStylesheetScript extends Script {
+  page: never;
+  environment: never;
+  runPhase: never;
+
+  execute() {
+    const searchParams = new URLSearchParams(document.location.search);
+    if (searchParams.has(kImportParam)) {
+      injectStylesheet(chrome.runtime.getURL('css/workflow_import.css'));
+    }
+  }
+}
diff --git a/src/features/workflows/templates/workflows.html.ejs b/src/features/workflows/presentation/templates/workflows.html.ejs
similarity index 100%
rename from src/features/workflows/templates/workflows.html.ejs
rename to src/features/workflows/presentation/templates/workflows.html.ejs
diff --git a/src/features/workflows/scripts/dependenciesSetUpAtMain.script.ts b/src/features/workflows/scripts/dependenciesSetUpAtMain.script.ts
deleted file mode 100644
index 37ec85f..0000000
--- a/src/features/workflows/scripts/dependenciesSetUpAtMain.script.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import {
-  Dependency,
-  WorkflowsDependency,
-} from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
-import {
-  ScriptEnvironment,
-  ScriptPage,
-  ScriptRunPhase,
-} from '../../../common/architecture/scripts/Script';
-import SetUpDependenciesScript from '../../../common/architecture/scripts/setUpDependencies/SetUpDependenciesScript';
-
-export default class WorkflowsDependenciesSetUpAtMainScript extends SetUpDependenciesScript {
-  public page = ScriptPage.CommunityConsole;
-  public environment = ScriptEnvironment.ContentScript;
-  public runPhase = ScriptRunPhase.Main;
-  public dependencies: Dependency[] = [WorkflowsDependency];
-}
diff --git a/src/features/workflows/scripts/dependenciesSetUpAtStart.script.ts b/src/features/workflows/scripts/dependenciesSetUpAtStart.script.ts
deleted file mode 100644
index 0b52082..0000000
--- a/src/features/workflows/scripts/dependenciesSetUpAtStart.script.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import {
-  Dependency,
-  WorkflowsImportDependency,
-} from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
-import {
-  ScriptEnvironment,
-  ScriptPage,
-  ScriptRunPhase,
-} from '../../../common/architecture/scripts/Script';
-import SetUpDependenciesScript from '../../../common/architecture/scripts/setUpDependencies/SetUpDependenciesScript';
-
-export default class WorkflowsDependenciesSetUpAtStartScript extends SetUpDependenciesScript {
-  public priority = 102;
-  public page = ScriptPage.CommunityConsole;
-  public environment = ScriptEnvironment.ContentScript;
-  public runPhase = ScriptRunPhase.Start;
-  public dependencies: Dependency[] = [WorkflowsImportDependency];
-}
diff --git a/src/features/workflows/scripts/nodeWatcher.script.ts b/src/features/workflows/scripts/nodeWatcher.script.ts
deleted file mode 100644
index 36c5a75..0000000
--- a/src/features/workflows/scripts/nodeWatcher.script.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import DependenciesProviderSingleton, {
-  WorkflowsDependency,
-  WorkflowsImportDependency,
-} from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
-import {
-  ScriptEnvironment,
-  ScriptPage,
-  ScriptRunPhase,
-} from '../../../common/architecture/scripts/Script';
-import LegacyNodeWatcherScript from '../../../common/architecture/scripts/nodeWatcher/LegacyNodeWatcherScript';
-import WorkflowsImport from '../core/communityConsole/import';
-import Workflows from '../core/communityConsole/workflows';
-import WorkflowsImportCRTagsHandler from '../nodeWatcherHandlers/crTags.handler';
-import WorkflowsThreadListActionBarHandler from '../nodeWatcherHandlers/threadListActionBar.handler';
-
-export interface WorkflowsNodeWatcherDependencies {
-  workflows: Workflows;
-  workflowsImport: WorkflowsImport;
-}
-
-export default class WorkflowsNodeWatcherScript extends LegacyNodeWatcherScript<WorkflowsNodeWatcherDependencies> {
-  public page = ScriptPage.CommunityConsole;
-  public environment = ScriptEnvironment.ContentScript;
-  public runPhase = ScriptRunPhase.Main;
-  public handlers = new Map([
-    ['workflowsImportCRTags', WorkflowsImportCRTagsHandler],
-    ['workflowsThreadListActionBar', WorkflowsThreadListActionBarHandler],
-  ]);
-
-  protected optionsFactory(): WorkflowsNodeWatcherDependencies {
-    const dependenciesProvider = DependenciesProviderSingleton.getInstance();
-    return {
-      workflows: dependenciesProvider.getDependency(WorkflowsDependency),
-      workflowsImport: dependenciesProvider.getDependency(
-        WorkflowsImportDependency,
-      ),
-    };
-  }
-}
diff --git a/src/features/workflows/workflows.feature.ts b/src/features/workflows/workflows.feature.ts
deleted file mode 100644
index 9033ecb..0000000
--- a/src/features/workflows/workflows.feature.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import Feature from '../../common/architecture/features/Feature';
-import { ConcreteScript } from '../../common/architecture/scripts/Script';
-import { OptionCodename } from '../../common/options/optionsPrototype';
-import WorkflowsDependenciesSetUpAtMainScript from './scripts/dependenciesSetUpAtMain.script';
-import WorkflowsDependenciesSetUpAtStartScript from './scripts/dependenciesSetUpAtStart.script';
-import WorkflowsNodeWatcherScript from './scripts/nodeWatcher.script';
-
-export default class WorkflowsFeature extends Feature {
-  public readonly scripts: ConcreteScript[] = [
-    WorkflowsDependenciesSetUpAtStartScript,
-    WorkflowsDependenciesSetUpAtMainScript,
-    WorkflowsNodeWatcherScript,
-  ];
-
-  readonly codename = 'workflows';
-  readonly relatedOptions: OptionCodename[] = ['workflows'];
-}
diff --git a/webpack.config.js b/webpack.config.js
index c3e835d..1eb49ba 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -144,7 +144,8 @@
       }),
       new HtmlWebpackPlugin({
         filename: 'workflows.html',
-        template: 'src/features/workflows/templates/workflows.html.ejs',
+        template:
+            'src/features/workflows/presentation/templates/workflows.html.ejs',
         chunks: ['workflowManager'],
       }),
       new webpack.DefinePlugin({