Browse Source

feat: more detailed workflow log

Ahmad Kholid 3 years ago
parent
commit
dab82e7984
33 changed files with 215 additions and 90 deletions
  1. 1 0
      package.json
  2. 1 1
      src/background/collection-engine/flow-handler.js
  3. 2 2
      src/background/index.js
  4. 1 1
      src/background/workflow-engine/blocks-handler/handler-browser-event.js
  5. 1 1
      src/background/workflow-engine/blocks-handler/handler-clipboard.js
  6. 10 2
      src/background/workflow-engine/blocks-handler/handler-conditions.js
  7. 2 2
      src/background/workflow-engine/blocks-handler/handler-execute-workflow.js
  8. 1 1
      src/background/workflow-engine/blocks-handler/handler-export-data.js
  9. 1 1
      src/background/workflow-engine/blocks-handler/handler-google-sheets.js
  10. 1 1
      src/background/workflow-engine/blocks-handler/handler-handle-download.js
  11. 8 3
      src/background/workflow-engine/blocks-handler/handler-insert-data.js
  12. 1 1
      src/background/workflow-engine/blocks-handler/handler-interaction-block.js
  13. 2 2
      src/background/workflow-engine/blocks-handler/handler-javascript-code.js
  14. 1 1
      src/background/workflow-engine/blocks-handler/handler-loop-data.js
  15. 2 3
      src/background/workflow-engine/blocks-handler/handler-take-screenshot.js
  16. 2 2
      src/background/workflow-engine/blocks-handler/handler-webhook.js
  17. 49 9
      src/background/workflow-engine/engine.js
  18. 1 1
      src/components/block/BlockElementExists.vue
  19. 1 1
      src/components/newtab/workflow/edit/EditClipboard.vue
  20. 16 2
      src/components/ui/UiExpand.vue
  21. 1 1
      src/content/blocks-handler/handler-event-click.js
  22. 1 1
      src/content/blocks-handler/handler-forms.js
  23. 1 1
      src/content/blocks-handler/handler-hover-element.js
  24. 1 1
      src/content/blocks-handler/handler-trigger-event.js
  25. 1 1
      src/content/element-selector/App.vue
  26. 3 3
      src/content/element-selector/AppBlocks.vue
  27. 1 1
      src/models/workflow.js
  28. 0 1
      src/newtab/App.vue
  29. 64 30
      src/newtab/pages/logs/[id].vue
  30. 13 6
      src/utils/reference-data/index.js
  31. 23 5
      src/utils/reference-data/mustache-replacer.js
  32. 1 1
      src/utils/workflow-data.js
  33. 1 1
      yarn.lock

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
   },
   "dependencies": {
     "@codemirror/basic-setup": "^0.19.1",
+    "@codemirror/fold": "^0.19.3",
     "@codemirror/lang-javascript": "^0.19.7",
     "@codemirror/lang-json": "^0.19.2",
     "@codemirror/theme-one-dark": "^0.19.1",

+ 1 - 1
src/background/collection-engine/flow-handler.js

@@ -1,6 +1,6 @@
+import dataExporter from '@/utils/data-exporter';
 import WorkflowEngine from '../workflow-engine/engine';
 import blocksHandler from '../workflow-engine/blocks-handler';
-import dataExporter from '@/utils/data-exporter';
 
 export function workflow(flow) {
   return new Promise((resolve, reject) => {

+ 2 - 2
src/background/index.js

@@ -1,14 +1,14 @@
 import browser from 'webextension-polyfill';
 import { MessageListener } from '@/utils/message';
-import { registerSpecificDay } from '../utils/workflow-trigger';
 import { parseJSON, findTriggerBlock } from '@/utils/helper';
 import getFile from '@/utils/get-file';
+import decryptFlow, { getWorkflowPass } from '@/utils/decrypt-flow';
+import { registerSpecificDay } from '../utils/workflow-trigger';
 import WorkflowState from './workflow-state';
 import CollectionEngine from './collection-engine';
 import WorkflowEngine from './workflow-engine/engine';
 import blocksHandler from './workflow-engine/blocks-handler';
 import WorkflowLogger from './workflow-logger';
-import decryptFlow, { getWorkflowPass } from '@/utils/decrypt-flow';
 
 const validateUrl = (str) => str?.startsWith('http');
 const storage = {

+ 1 - 1
src/background/workflow-engine/blocks-handler/handler-browser-event.js

@@ -1,6 +1,6 @@
 import browser from 'webextension-polyfill';
-import { getBlockConnection } from '../helper';
 import { isWhitespace } from '@/utils/helper';
+import { getBlockConnection } from '../helper';
 
 function handleEventListener(target, validate) {
   return (data, activeTab) => {

+ 1 - 1
src/background/workflow-engine/blocks-handler/handler-clipboard.js

@@ -21,7 +21,7 @@ export default async function ({ data, outputs }) {
     const copiedText = textarea.value;
 
     if (data.assignVariable) {
-      this.referenceData.variables[data.variableName] = copiedText;
+      this.setVariable(data.variableName, copiedText);
     }
     if (data.saveData) {
       this.addDataToColumn(data.dataColumn, copiedText);

+ 10 - 2
src/background/workflow-engine/blocks-handler/handler-conditions.js

@@ -1,6 +1,6 @@
-import { getBlockConnection } from '../helper';
 import compareBlockValue from '@/utils/compare-block-value';
 import mustacheReplacer from '@/utils/reference-data/mustache-replacer';
+import { getBlockConnection } from '../helper';
 
 function conditions({ data, outputs }, { prevBlockData, refData }) {
   return new Promise((resolve, reject) => {
@@ -12,6 +12,7 @@ function conditions({ data, outputs }, { prevBlockData, refData }) {
     let resultData = '';
     let isConditionMatch = false;
     let outputIndex = data.conditions.length + 1;
+    const replacedValue = {};
     const prevData = Array.isArray(prevBlockData)
       ? prevBlockData[0]
       : prevBlockData;
@@ -22,7 +23,13 @@ function conditions({ data, outputs }, { prevBlockData, refData }) {
       const firstValue = mustacheReplacer(compareValue ?? prevData, refData);
       const secondValue = mustacheReplacer(value, refData);
 
-      const isMatch = compareBlockValue(type, firstValue, secondValue);
+      Object.assign(replacedValue, firstValue.list, secondValue.list);
+
+      const isMatch = compareBlockValue(
+        type,
+        firstValue.value,
+        secondValue.value
+      );
 
       if (isMatch) {
         resultData = value;
@@ -32,6 +39,7 @@ function conditions({ data, outputs }, { prevBlockData, refData }) {
     });
 
     resolve({
+      replacedValue,
       data: resultData,
       nextBlockId: getBlockConnection({ outputs }, outputIndex),
     });

+ 2 - 2
src/background/workflow-engine/blocks-handler/handler-execute-workflow.js

@@ -1,8 +1,8 @@
 import browser from 'webextension-polyfill';
-import WorkflowEngine from '../engine';
-import { getBlockConnection } from '../helper';
 import { isWhitespace, parseJSON } from '@/utils/helper';
 import decryptFlow, { getWorkflowPass } from '@/utils/decrypt-flow';
+import WorkflowEngine from '../engine';
+import { getBlockConnection } from '../helper';
 
 function workflowListener(workflow, options) {
   return new Promise((resolve, reject) => {

+ 1 - 1
src/background/workflow-engine/blocks-handler/handler-export-data.js

@@ -1,6 +1,6 @@
 import browser from 'webextension-polyfill';
-import { getBlockConnection } from '../helper';
 import { default as dataExporter, files } from '@/utils/data-exporter';
+import { getBlockConnection } from '../helper';
 
 async function exportData({ data, outputs }) {
   const nextBlockId = getBlockConnection({ outputs });

+ 1 - 1
src/background/workflow-engine/blocks-handler/handler-google-sheets.js

@@ -1,4 +1,3 @@
-import { getBlockConnection } from '../helper';
 import { googleSheets } from '@/utils/api';
 import {
   convert2DArrayToArrayObj,
@@ -6,6 +5,7 @@ import {
   isWhitespace,
   parseJSON,
 } from '@/utils/helper';
+import { getBlockConnection } from '../helper';
 
 async function getSpreadsheetValues({ spreadsheetId, range, firstRowAsKey }) {
   const response = await googleSheets.getValues({ spreadsheetId, range });

+ 1 - 1
src/background/workflow-engine/blocks-handler/handler-handle-download.js

@@ -51,7 +51,7 @@ function handleDownload({ data, outputs }) {
         this.addDataToColumn(data.dataColumn, currentFilename);
       }
       if (data.assignVariable) {
-        this.referenceData.variables[data.variableName] = currentFilename;
+        this.setVariable(data.variableName, currentFilename);
       }
 
       clearTimeout(timeout);

+ 8 - 3
src/background/workflow-engine/blocks-handler/handler-insert-data.js

@@ -1,21 +1,26 @@
-import { getBlockConnection } from '../helper';
 import { parseJSON } from '@/utils/helper';
 import mustacheReplacer from '@/utils/reference-data/mustache-replacer';
+import { getBlockConnection } from '../helper';
 
 function insertData({ outputs, data }, { refData }) {
   return new Promise((resolve) => {
+    const replacedValueList = {};
     data.dataList.forEach(({ name, value, type }) => {
       const replacedValue = mustacheReplacer(value, refData);
-      const realValue = parseJSON(replacedValue, replacedValue);
+      const realValue = parseJSON(replacedValue.value, replacedValue.value);
+
+      Object.assign(replacedValueList, replacedValue.list);
 
       if (type === 'table') {
         this.addDataToColumn(name, realValue);
       } else {
-        this.referenceData.variables[name] = realValue;
+        this.setVariable(name, realValue);
       }
     });
 
     resolve({
+      data: '',
+      replacedValue: replacedValueList,
       nextBlockId: getBlockConnection({ outputs }),
     });
   });

+ 1 - 1
src/background/workflow-engine/blocks-handler/handler-interaction-block.js

@@ -64,7 +64,7 @@ async function interactionHandler(block) {
     }
 
     if (block.data.assignVariable) {
-      this.referenceData.variables[block.data.variableName] = data;
+      this.setVariable(block.data.variableName, data);
     }
 
     return {

+ 2 - 2
src/background/workflow-engine/blocks-handler/handler-javascript-code.js

@@ -22,10 +22,10 @@ export async function javascriptCode({ outputs, data, ...block }, { refData }) {
     }
 
     const result = await this._sendMessageToTab({ ...block, data, refData });
-    console.log(result);
+
     if (result?.variables) {
       Object.keys(result.variables).forEach((varName) => {
-        this.referenceData.variables[varName] = result.variables[varName];
+        this.setVariable(varName, result.variables[varName]);
       });
     }
     if (result?.columns.insert) {

+ 1 - 1
src/background/workflow-engine/blocks-handler/handler-loop-data.js

@@ -1,5 +1,5 @@
-import { getBlockConnection } from '../helper';
 import { parseJSON } from '@/utils/helper';
+import { getBlockConnection } from '../helper';
 
 async function loopData({ data, id, outputs }) {
   const nextBlockId = getBlockConnection({ outputs });

+ 2 - 3
src/background/workflow-engine/blocks-handler/handler-take-screenshot.js

@@ -1,6 +1,6 @@
 import browser from 'webextension-polyfill';
-import { getBlockConnection } from '../helper';
 import { fileSaver } from '@/utils/helper';
+import { getBlockConnection } from '../helper';
 
 function saveImage({ fileName, uri, ext }) {
   const image = new Image();
@@ -35,8 +35,7 @@ async function takeScreenshot({ data, outputs, name }) {
       if (data.saveToColumn) this.addDataToColumn(data.dataColumn, dataUrl);
       if (saveToComputer)
         saveImage({ fileName: data.fileName, uri: dataUrl, ext: data.ext });
-      if (data.assignVariable)
-        this.referenceData.variables[data.variableName] = dataUrl;
+      if (data.assignVariable) this.setVariable(data.variableName, dataUrl);
     };
 
     if (data.captureActiveTab) {

+ 2 - 2
src/background/workflow-engine/blocks-handler/handler-webhook.js

@@ -1,7 +1,7 @@
 import objectPath from 'object-path';
-import { getBlockConnection } from '../helper';
 import { isWhitespace } from '@/utils/helper';
 import { executeWebhook } from '@/utils/webhookUtil';
+import { getBlockConnection } from '../helper';
 
 export async function webhook({ data, outputs }) {
   const nextBlockId = getBlockConnection({ outputs });
@@ -42,7 +42,7 @@ export async function webhook({ data, outputs }) {
     }
 
     if (data.assignVariable) {
-      this.referenceData.variables[data.variableName] = returnData;
+      this.setVariable(data.variableName, returnData);
     }
     if (data.saveData) {
       this.addDataToColumn(data.dataColumn, returnData);

+ 49 - 9
src/background/workflow-engine/engine.js

@@ -39,6 +39,7 @@ class WorkflowEngine {
     this.blocks = {};
     this.history = [];
     this.columnsId = {};
+    this.historyCtxData = {};
     this.eventListeners = {};
     this.preloadScripts = [];
     this.columns = { column: { index: 0, name: 'column', type: 'any' } };
@@ -97,6 +98,7 @@ class WorkflowEngine {
     this.isUsingProxy = false;
 
     this.history = [];
+    this.preloadScripts = [];
     this.columns = { column: { index: 0, name: 'column', type: 'any' } };
 
     this.activeTab = {
@@ -205,6 +207,26 @@ class WorkflowEngine {
     )
       return;
 
+    const historyId = nanoid();
+    detail.id = historyId;
+
+    if (tasks[detail.name]?.refDataKeys && this.saveLog) {
+      const { activeTabUrl, loopData, prevBlockData } = JSON.parse(
+        JSON.stringify(this.referenceData)
+      );
+
+      this.historyCtxData[historyId] = {
+        referenceData: {
+          loopData,
+          activeTabUrl,
+          prevBlockData,
+        },
+        replacedValue: detail.replacedValue,
+      };
+
+      delete detail.replacedValue;
+    }
+
     this.history.push(detail);
   }
 
@@ -237,6 +259,10 @@ class WorkflowEngine {
     currentColumn.index += 1;
   }
 
+  setVariable(name, value) {
+    this.referenceData.variables[name] = value;
+  }
+
   async stop() {
     try {
       if (this.childWorkflowId) {
@@ -286,6 +312,11 @@ class WorkflowEngine {
       if (!this.workflow.isTesting) {
         const { name, id } = this.workflow;
 
+        let { logsCtxData } = await browser.storage.local.get('logsCtxData');
+        if (!logsCtxData) logsCtxData = {};
+        logsCtxData[this.id] = this.historyCtxData;
+        await browser.storage.local.set({ logsCtxData });
+
         await this.logger.add({
           name,
           status,
@@ -313,16 +344,18 @@ class WorkflowEngine {
         currentBlock: this.currentBlock,
       });
 
-      browser.storage.local.set({
-        [`last-state:${this.workflow.id}`]: {
-          columns: this.columns,
-          referenceData: {
-            table: this.referenceData.table,
-            variables: this.referenceData.variables,
-            globalData: this.referenceData.globalData,
+      if (this.workflow.settings.reuseLastState) {
+        browser.storage.local.set({
+          [`last-state:${this.workflow.id}`]: {
+            columns: this.columns,
+            referenceData: {
+              table: this.referenceData.table,
+              variables: this.referenceData.variables,
+              globalData: this.referenceData.globalData,
+            },
           },
-        },
-      });
+        });
+      }
 
       this.isDestroyed = true;
       this.eventListeners = {};
@@ -375,10 +408,15 @@ class WorkflowEngine {
         refData: this.referenceData,
       });
 
+      if (result.replacedValue)
+        replacedBlock.replacedValue = result.replacedValue;
+
       this.addLogHistory({
         name: block.name,
         logId: result.logId,
         type: result.status || 'success',
+        description: block.data.description,
+        replacedValue: replacedBlock.replacedValue,
         duration: Math.round(Date.now() - startExecuteTime),
       });
 
@@ -399,6 +437,8 @@ class WorkflowEngine {
         type: 'error',
         message: error.message,
         name: block.name,
+        description: block.data.description,
+        replacedValue: replacedBlock.replacedValue,
         ...(error.data || {}),
       });
 

+ 1 - 1
src/components/block/BlockElementExists.vue

@@ -36,9 +36,9 @@
 <script setup>
 import { useI18n } from 'vue-i18n';
 import emitter from '@/lib/mitt';
-import BlockBase from './BlockBase.vue';
 import { useComponentId } from '@/composable/componentId';
 import { useEditorBlock } from '@/composable/editorBlock';
+import BlockBase from './BlockBase.vue';
 
 const props = defineProps({
   editor: {

+ 1 - 1
src/components/newtab/workflow/edit/EditClipboard.vue

@@ -24,8 +24,8 @@
 </template>
 <script setup>
 import { useI18n } from 'vue-i18n';
-import InsertWorkflowData from './InsertWorkflowData.vue';
 import { useHasPermissions } from '@/composable/hasPermissions';
+import InsertWorkflowData from './InsertWorkflowData.vue';
 
 const props = defineProps({
   data: {

+ 16 - 2
src/components/ui/UiExpand.vue

@@ -1,13 +1,22 @@
 <template>
   <div :aria-expanded="show" class="ui-expand">
-    <button :class="headerClass" @click="toggleExpand">
+    <button
+      :class="[headerClass, { [headerActiveClass]: show }]"
+      @click="toggleExpand"
+    >
       <v-remixicon
-        v-if="!hideHeaderIcon"
+        v-if="!hideHeaderIcon && !appendIcon"
         :rotate="show ? 90 : -90"
         name="riArrowLeftSLine"
         class="mr-2 transition-transform -ml-1"
       />
       <slot v-bind="{ show }" name="header" />
+      <v-remixicon
+        v-if="appendIcon"
+        :rotate="show ? 90 : -90"
+        name="riArrowLeftSLine"
+        class="mr-2 transition-transform -ml-1"
+      />
     </button>
     <transition-expand>
       <div v-if="show" :class="panelClass" class="ui-expand__panel">
@@ -32,10 +41,15 @@ const props = defineProps({
     type: String,
     default: 'px-4 py-2 w-full flex items-center h-full',
   },
+  headerActiveClass: {
+    type: String,
+    default: '',
+  },
   hideHeaderIcon: {
     type: Boolean,
     default: false,
   },
+  appendIcon: Boolean,
 });
 const emit = defineEmits(['update:modelValue']);
 

+ 1 - 1
src/content/blocks-handler/handler-event-click.js

@@ -1,5 +1,5 @@
-import handleSelector from '../handle-selector';
 import { sendMessage } from '@/utils/message';
+import handleSelector from '../handle-selector';
 
 function eventClick(block) {
   return new Promise((resolve, reject) => {

+ 1 - 1
src/content/blocks-handler/handler-forms.js

@@ -1,5 +1,5 @@
-import handleSelector, { markElement } from '../handle-selector';
 import handleFormElement from '@/utils/handle-form-element';
+import handleSelector, { markElement } from '../handle-selector';
 
 async function forms(block) {
   const { data } = block;

+ 1 - 1
src/content/blocks-handler/handler-hover-element.js

@@ -1,5 +1,5 @@
-import handleSelector from '../handle-selector';
 import { sendMessage } from '@/utils/message';
+import handleSelector from '../handle-selector';
 
 function eventClick(block) {
   return new Promise((resolve, reject) => {

+ 1 - 1
src/content/blocks-handler/handler-trigger-event.js

@@ -1,7 +1,7 @@
-import handleSelector from '../handle-selector';
 import { sendMessage } from '@/utils/message';
 import simulateEvent from '@/utils/simulate-event';
 import simulateMouseEvent from '@/utils/simulate-event/mouse-event';
+import handleSelector from '../handle-selector';
 
 const modifiers = {
   altKey: 1,

+ 1 - 1
src/content/element-selector/App.vue

@@ -153,10 +153,10 @@
 import { reactive, ref, watch, inject, nextTick } from 'vue';
 import { getCssSelector } from 'css-selector-generator';
 import { debounce } from '@/utils/helper';
+import findElement from '@/utils/find-element';
 import AppBlocks from './AppBlocks.vue';
 import AppSelector from './AppSelector.vue';
 import AppElementList from './AppElementList.vue';
-import findElement from '@/utils/find-element';
 
 const selectedElement = {
   path: [],

+ 3 - 3
src/content/element-selector/AppBlocks.vue

@@ -36,14 +36,14 @@
 <script setup>
 import { shallowReactive } from 'vue';
 import { tasks } from '@/utils/shared';
+import EditForms from '@/components/newtab/workflow/edit/EditForms.vue';
+import EditTriggerEvent from '@/components/newtab/workflow/edit/EditTriggerEvent.vue';
+import EditScrollElement from '@/components/newtab/workflow/edit/EditScrollElement.vue';
 import handleForms from '../blocks-handler/handler-forms';
 import handleGetText from '../blocks-handler/handler-get-text';
 import handleEventClick from '../blocks-handler/handler-event-click';
 import handelTriggerEvent from '../blocks-handler/handler-trigger-event';
 import handleElementScroll from '../blocks-handler/handler-element-scroll';
-import EditForms from '@/components/newtab/workflow/edit/EditForms.vue';
-import EditTriggerEvent from '@/components/newtab/workflow/edit/EditTriggerEvent.vue';
-import EditScrollElement from '@/components/newtab/workflow/edit/EditScrollElement.vue';
 
 const props = defineProps({
   selector: {

+ 1 - 1
src/models/workflow.js

@@ -1,10 +1,10 @@
 import { Model } from '@vuex-orm/core';
 import { nanoid } from 'nanoid';
 import browser from 'webextension-polyfill';
-import Log from './log';
 import { cleanWorkflowTriggers } from '@/utils/workflow-trigger';
 import { fetchApi } from '@/utils/api';
 import decryptFlow, { getWorkflowPass } from '@/utils/decrypt-flow';
+import Log from './log';
 
 class Workflow extends Model {
   static entity = 'workflows';

+ 0 - 1
src/newtab/App.vue

@@ -208,7 +208,6 @@ window.addEventListener('beforeunload', () => {
 (async () => {
   try {
     const { isFirstTime } = await browser.storage.local.get('isFirstTime');
-
     isUpdated.value = !isFirstTime && compare(currentVersion, prevVersion, '>');
 
     await Promise.allSettled([

+ 64 - 30
src/newtab/pages/logs/[id].vue

@@ -32,37 +32,63 @@
             <v-remixicon name="riArrowLeftLine" class="mr-2" />
             {{ t('log.goBack', { name: collectionLog.name }) }}
           </router-link>
-          <ui-list-item v-for="(item, index) in history" :key="index">
-            <span
-              :class="logsType[item.type]?.color"
-              class="p-1 rounded-lg align-middle inline-block mr-2 dark:text-black"
-            >
-              <v-remixicon :name="logsType[item.type]?.icon" size="20" />
-            </span>
-            <div class="flex-1 line-clamp pr-2">
-              <p class="w-full text-overflow leading-tight">
-                {{ item.name }}
-              </p>
-              <p
-                v-if="item.message"
-                :title="item.message"
-                class="text-sm line-clamp text-gray-600 dark:text-gray-200"
+          <ui-expand
+            v-for="(item, index) in history"
+            :key="item.id || index"
+            hide-header-icon
+            class="mb-1"
+            header-active-class="bg-box-transparent rounded-b-none"
+            header-class="flex items-center px-4 py-2 hoverable rounded-lg w-full text-left history-item focus:ring-0"
+          >
+            <template #header="{ show }">
+              <v-remixicon
+                :rotate="show ? 270 : 180"
+                size="20"
+                name="riArrowLeftSLine"
+                class="transition-transform dark:text-gray-200 text-gray-600 -ml-1 mr-2"
+              />
+              <span
+                :class="logsType[item.type]?.color"
+                class="p-1 rounded-lg align-middle inline-block mr-2 dark:text-black"
               >
-                {{ item.message }}
+                <v-remixicon :name="logsType[item.type]?.icon" size="20" />
+              </span>
+              <div class="flex-1 line-clamp pr-2">
+                <p class="w-full text-overflow leading-tight">
+                  {{ item.name }}
+                  <span
+                    v-show="item.description"
+                    :title="item.description"
+                    class="text-overflow text-gray-600 dark:text-gray-200 text-sm"
+                  >
+                    ({{ item.description }})
+                  </span>
+                </p>
+                <p
+                  v-if="item.message"
+                  :title="item.message"
+                  class="text-sm line-clamp text-gray-600 dark:text-gray-200"
+                >
+                  {{ item.message }}
+                </p>
+              </div>
+              <router-link
+                v-if="item.logId"
+                :to="'/logs/' + item.logId"
+                class="mr-4"
+                title="Open log detail"
+              >
+                <v-remixicon name="riExternalLinkLine" />
+              </router-link>
+              <p class="text-gray-600 dark:text-gray-200">
+                {{ countDuration(0, item.duration || 0) }}
               </p>
-            </div>
-            <router-link
-              v-if="item.logId"
-              :to="'/logs/' + item.logId"
-              class="mr-4"
-              title="Open log detail"
+            </template>
+            <pre
+              class="text-sm px-4 max-h-52 overflow-auto scroll bg-box-transparent pb-2 rounded-b-lg"
+              >{{ ctxData[item.id] }}</pre
             >
-              <v-remixicon name="riExternalLinkLine" />
-            </router-link>
-            <p class="text-gray-600 dark:text-gray-200">
-              {{ countDuration(0, item.duration || 0) }}
-            </p>
-          </ui-list-item>
+          </ui-expand>
         </ui-list>
         <div
           v-if="activeLog.history.length >= 10"
@@ -102,9 +128,10 @@
   </div>
 </template>
 <script setup>
-import { computed, onMounted, shallowReactive } from 'vue';
+import { computed, onMounted, shallowReactive, shallowRef } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { useI18n } from 'vue-i18n';
+import browser from 'webextension-polyfill';
 import Log from '@/models/log';
 import dayjs from '@/lib/dayjs';
 import { countDuration } from '@/utils/helper';
@@ -137,6 +164,7 @@ const { t, te } = useI18n();
 const route = useRoute();
 const router = useRouter();
 
+const ctxData = shallowRef({});
 const pagination = shallowReactive({
   perPage: 10,
   currentPage: 1,
@@ -191,8 +219,14 @@ function deleteLog() {
   });
 }
 
-onMounted(() => {
+onMounted(async () => {
   if (!activeLog.value) router.replace('/logs');
+
+  const { logsCtxData } = await browser.storage.local.get('logsCtxData');
+  const logCtxData = logsCtxData && logsCtxData[route.params.id];
+  if (logCtxData) {
+    ctxData.value = logCtxData;
+  }
 });
 </script>
 <style>

+ 13 - 6
src/utils/reference-data/index.js

@@ -5,6 +5,11 @@ export default function ({ block, refKeys, data }) {
   if (!refKeys || refKeys.length === 0) return block;
 
   const copyBlock = JSON.parse(JSON.stringify(block));
+  const addReplacedValue = (value) => {
+    if (!copyBlock.replacedValue) copyBlock.replacedValue = {};
+
+    copyBlock.replacedValue = { ...copyBlock.replacedValue, ...value };
+  };
 
   refKeys.forEach((blockDataKey) => {
     const currentData = objectPath.get(copyBlock.data, blockDataKey);
@@ -13,18 +18,20 @@ export default function ({ block, refKeys, data }) {
 
     if (Array.isArray(currentData)) {
       currentData.forEach((str, index) => {
+        const replacedStr = mustacheReplacer(str, data);
+
+        addReplacedValue(replacedStr.list);
         objectPath.set(
           copyBlock.data,
           `${blockDataKey}.${index}`,
-          mustacheReplacer(str, data)
+          replacedStr.value
         );
       });
     } else if (typeof currentData === 'string') {
-      objectPath.set(
-        copyBlock.data,
-        blockDataKey,
-        mustacheReplacer(currentData, data)
-      );
+      const replacedStr = mustacheReplacer(currentData, data);
+
+      addReplacedValue(replacedStr.list);
+      objectPath.set(copyBlock.data, blockDataKey, replacedStr.value);
     }
   });
 

+ 23 - 5
src/utils/reference-data/mustache-replacer.js

@@ -92,7 +92,12 @@ export function keyParser(key, data) {
 }
 
 function replacer(str, { regex, tagLen, modifyPath, data }) {
-  return str.replace(regex, (match) => {
+  const replaceResult = {
+    list: {},
+    value: str,
+  };
+
+  replaceResult.value = str.replace(regex, (match) => {
     let key = match.slice(tagLen, -tagLen).trim();
 
     if (!key) return '';
@@ -114,25 +119,38 @@ function replacer(str, { regex, tagLen, modifyPath, data }) {
       result = objectPath.get(data[dataKey], path) ?? match;
     }
 
-    return typeof result === 'string' ? result : JSON.stringify(result);
+    result = typeof result === 'string' ? result : JSON.stringify(result);
+    replaceResult.list[match] = result;
+
+    return result;
   });
+
+  return replaceResult;
 }
 
 export default function (str, refData) {
   if (!str || typeof str !== 'string') return '';
 
   const data = { ...refData, functions };
+  const replacedList = {};
+
   const replacedStr = replacer(`${str}`, {
     data,
     tagLen: 2,
     regex: /\{\{(.*?)\}\}/g,
-    modifyPath: (path) =>
-      replacer(path, {
+    modifyPath: (path) => {
+      const { value, list } = replacer(path, {
         data,
         tagLen: 1,
         regex: /\[(.*?)\]/g,
-      }),
+      });
+      Object.assign(replacedList, list);
+
+      return value;
+    },
   });
 
+  Object.assign(replacedStr.list, replacedList);
+
   return replacedStr;
 }

+ 1 - 1
src/utils/workflow-data.js

@@ -1,5 +1,5 @@
-import { parseJSON, fileSaver, openFilePicker, isObject } from './helper';
 import Workflow from '@/models/workflow';
+import { parseJSON, fileSaver, openFilePicker, isObject } from './helper';
 
 export function importWorkflow(attrs = {}) {
   openFilePicker(['application/json'], attrs)

+ 1 - 1
yarn.lock

@@ -961,7 +961,7 @@
     "@codemirror/text" "^0.19.0"
     "@codemirror/view" "^0.19.0"
 
-"@codemirror/fold@^0.19.0":
+"@codemirror/fold@^0.19.0", "@codemirror/fold@^0.19.3":
   version "0.19.3"
   resolved "https://registry.yarnpkg.com/@codemirror/fold/-/fold-0.19.3.tgz#de55d44a7313f2a8920aefb6ebf9eff34715d8d4"
   integrity sha512-8hT+Eq2G68mL0yPRvSD2ewhnLQAX6sbUJmtGVKFcj8oAXtfpYCX8LIcfXsuI19Qs7gZkOSpqZvn+KKj8IhZoAw==