ソースを参照

feat: add update google sheet values option

Ahmad Kholid 3 年 前
コミット
e254928f34

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

@@ -1,21 +1,60 @@
 import { getBlockConnection } from '../helper';
-import { getGoogleSheetsValue } from '@/utils/api';
-import { convert2DArrayToArrayObj, isWhitespace } from '@/utils/helper';
+import { googleSheets } from '@/utils/api';
+import {
+  convert2DArrayToArrayObj,
+  convertArrObjTo2DArr,
+  isWhitespace,
+  parseJSON,
+} from '@/utils/helper';
 
-async function getSpreadsheetValues(data) {
-  const response = await getGoogleSheetsValue(data.spreadsheetId, data.range);
+async function getSpreadsheetValues({ spreadsheetId, range, firstRowAsKey }) {
+  const response = await googleSheets.getValues({ spreadsheetId, range });
 
   if (response.status !== 200) {
     throw new Error(response.statusText);
   }
 
   const { values } = await response.json();
-  const sheetsData = data.firstRowAsKey
-    ? convert2DArrayToArrayObj(values)
-    : values;
+  const sheetsData = firstRowAsKey ? convert2DArrayToArrayObj(values) : values;
 
   return sheetsData;
 }
+async function updateSpreadsheetValues(
+  {
+    spreadsheetId,
+    range,
+    valueInputOption,
+    keysAsFirstRow,
+    dataFrom,
+    customData,
+  },
+  dataColumns
+) {
+  let values = [];
+
+  if (dataFrom === 'data-columns') {
+    if (keysAsFirstRow) {
+      values = convertArrObjTo2DArr(dataColumns);
+    } else {
+      values = dataColumns.map(Object.values);
+    }
+  } else if (dataFrom === 'custom') {
+    values = parseJSON(customData, customData);
+  }
+
+  const response = await googleSheets.updateValues({
+    range,
+    spreadsheetId,
+    valueInputOption,
+    options: {
+      body: JSON.stringify({ values }),
+    },
+  });
+
+  if (response.status !== 200) {
+    throw new Error(response.statusText);
+  }
+}
 
 export default async function ({ data, outputs }) {
   const nextBlockId = getBlockConnection({ outputs });
@@ -35,6 +74,11 @@ export default async function ({ data, outputs }) {
       if (data.refKey && !isWhitespace(data.refKey)) {
         this.referenceData.googleSheets[data.refKey] = spreadsheetValues;
       }
+    } else if (data.type === 'update') {
+      result = await updateSpreadsheetValues(
+        data,
+        this.referenceData.dataColumns
+      );
     }
 
     return {

+ 5 - 2
src/components/newtab/workflow/edit/EditGetText.vue

@@ -137,12 +137,15 @@ function updateData(value) {
   emit('update:data', { ...props.data, ...value });
 }
 function handleExpCheckbox(id, value) {
+  const copy = [...new Set(regexExp.value)];
+
   if (value) {
-    regexExp.value.push(id);
+    copy.push(id);
   } else {
-    regexExp.value.splice(regexExp.value.indexOf(id), 1);
+    copy.splice(copy.indexOf(id), 1);
   }
 
+  regexExp.value = copy;
   updateData({ regexExp: regexExp.value });
 }
 </script>

+ 77 - 13
src/components/newtab/workflow/edit/EditGoogleSheets.vue

@@ -7,7 +7,6 @@
       @change="updateData({ description: $event })"
     />
     <ui-select
-      v-if="false"
       :model-value="data.type"
       class="w-full mb-2"
       @change="updateData({ type: $event })"
@@ -15,8 +14,8 @@
       <option value="get">
         {{ t('workflow.blocks.google-sheets.select.get') }}
       </option>
-      <option value="write">
-        {{ t('workflow.blocks.google-sheets.select.write') }}
+      <option value="update">
+        {{ t('workflow.blocks.google-sheets.select.update') }}
       </option>
     </ui-select>
     <ui-input
@@ -88,17 +87,75 @@
         class="mt-4 max-h-96"
       />
     </template>
-    <template v-else-if="data.type === 'write'">
-      <pre>
-        halo
-      </pre>
+    <template v-else-if="data.type === 'update'">
+      <ui-select
+        :model-value="data.valueInputOption"
+        class="w-full mt-2"
+        @change="updateData({ valueInputOption: $event })"
+      >
+        <template #label>
+          {{ t('workflow.blocks.google-sheets.valueInputOption') }}
+          <a
+            href="https://developers.google.com/sheets/api/reference/rest/v4/ValueInputOption"
+            target="_blank"
+            rel="noopener"
+          >
+            <v-remixicon name="riInformationLine" size="18" class="inline" />
+          </a>
+        </template>
+        <option
+          v-for="option in valueInputOptions"
+          :key="option"
+          :value="option"
+        >
+          {{ option }}
+        </option>
+      </ui-select>
+      <ui-select
+        :model-value="data.dataFrom"
+        :label="t('workflow.blocks.google-sheets.dataFrom.label')"
+        class="w-full mt-2"
+        @change="updateData({ dataFrom: $event })"
+      >
+        <option v-for="item in dataFrom" :key="item" :value="item">
+          {{ t(`workflow.blocks.google-sheets.dataFrom.options.${item}`) }}
+        </option>
+      </ui-select>
+      <ui-checkbox
+        v-if="data.dataFrom === 'data-columns'"
+        :model-value="data.keysAsFirstRow"
+        class="mt-2"
+        @change="updateData({ keysAsFirstRow: $event })"
+      >
+        {{ t('workflow.blocks.google-sheets.keysAsFirstRow') }}
+      </ui-checkbox>
+      <ui-button
+        v-else
+        class="w-full mt-2"
+        variant="accent"
+        @click="customDataState.showModal = true"
+      >
+        {{ t('workflow.blocks.google-sheets.insertData') }}
+      </ui-button>
     </template>
+    <ui-modal
+      v-model="customDataState.showModal"
+      title="Custom data"
+      content-class="max-w-xl"
+    >
+      <shared-codemirror
+        v-model="customDataState.data"
+        style="height: calc(100vh - 10rem)"
+        lang="json"
+        @change="updateData({ customData: $event })"
+      />
+    </ui-modal>
   </div>
 </template>
 <script setup>
 import { shallowReactive } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { getGoogleSheetsValue } from '@/utils/api';
+import { googleSheets } from '@/utils/api';
 import { convert2DArrayToArrayObj } from '@/utils/helper';
 import SharedCodemirror from '@/components/newtab/shared/SharedCodemirror.vue';
 
@@ -112,10 +169,17 @@ const emit = defineEmits(['update:data']);
 
 const { t } = useI18n();
 
+const dataFrom = ['data-columns', 'custom'];
+const valueInputOptions = ['RAW', 'USER_ENTERED'];
+
 const previewDataState = shallowReactive({
+  data: '',
   status: 'idle',
   errorMessage: '',
-  data: '',
+});
+const customDataState = shallowReactive({
+  showModal: false,
+  data: props.data.customData,
 });
 
 function updateData(value) {
@@ -124,10 +188,10 @@ function updateData(value) {
 async function previewData() {
   try {
     previewDataState.status = 'loading';
-    const response = await getGoogleSheetsValue(
-      props.data.spreadsheetId,
-      props.data.range
-    );
+    const response = await googleSheets.getValues({
+      spreadsheetId: props.data.spreadsheetId,
+      range: props.data.range,
+    });
 
     if (response.status !== 200) {
       throw new Error(response.statusText);

+ 4 - 2
src/components/ui/UiSelect.vue

@@ -1,11 +1,13 @@
 <template>
   <div :class="{ 'inline-block': !block }" class="ui-select cursor-pointer">
     <label
-      v-if="label"
+      v-if="label || $slots.label"
       :for="selectId"
       class="text-gray-600 dark:text-gray-200 text-sm ml-2"
     >
-      {{ label }}
+      <slot name="label">
+        {{ label }}
+      </slot>
     </label>
     <div class="ui-select__content flex items-center w-full block relative">
       <v-remixicon

+ 11 - 1
src/locales/en/blocks.json

@@ -81,6 +81,16 @@
         "description": "Read Google Sheets data",
         "previewData": "Preview data",
         "firstRow": "Use the first row as keys",
+        "keysAsFirstRow": "Use keys as the first row",
+        "insertData": "Insert data",
+        "valueInputOption": "Value input option",
+        "dataFrom": {
+          "label": "Data from",
+          "options": {
+            "data-columns": "Data columns",
+            "custom": "Custom"
+          }
+        },
         "refKey": {
           "label": "Reference key",
           "placeholder": "Key name"
@@ -95,7 +105,7 @@
         },
         "select": {
           "get": "Get spreadsheet data",
-          "write": "Write spreadsheet data"
+          "update": "Update spreadsheet data"
         }
       },
       "active-tab": {

+ 19 - 4
src/utils/api.js

@@ -6,8 +6,23 @@ export function fetchApi(path, options) {
   return fetch(`${secrets.baseApiUrl}${urlPath}`, options);
 }
 
-export function getGoogleSheetsValue(spreadsheetId, range) {
-  const url = `/services/google-sheets?spreadsheetId=${spreadsheetId}&range=${range}`;
+export const googleSheets = {
+  getUrl(spreadsheetId, range) {
+    return `/services/google-sheets?spreadsheetId=${spreadsheetId}&range=${range}`;
+  },
+  getValues({ spreadsheetId, range }) {
+    const url = this.getUrl(spreadsheetId, range);
 
-  return fetchApi(url);
-}
+    return fetchApi(url);
+  },
+  updateValues({ spreadsheetId, range, valueInputOption, options = {} }) {
+    const url = `${this.getUrl(spreadsheetId, range)}&valueInputOption=${
+      valueInputOption || 'RAW'
+    }`;
+
+    return fetchApi(url, {
+      ...options,
+      method: 'PUT',
+    });
+  },
+};

+ 24 - 0
src/utils/helper.js

@@ -1,3 +1,27 @@
+export function convertArrObjTo2DArr(arr) {
+  const keyIndex = new Map();
+  const values = [[]];
+
+  arr.forEach((obj) => {
+    const keys = Object.keys(obj);
+    const row = [];
+
+    keys.forEach((key) => {
+      if (!keyIndex.has(key)) {
+        keyIndex.set(key, keyIndex.size);
+        values[0].push(key);
+      }
+
+      const rowIndex = keyIndex.get(key);
+      row[rowIndex] = obj[key];
+    });
+
+    values.push([...row]);
+  });
+
+  return values;
+}
+
 export function convert2DArrayToArrayObj(values) {
   let keyIndex = 0;
   const keys = values.shift();

+ 8 - 2
src/utils/reference-data/index.js

@@ -1,5 +1,5 @@
 import { set as setObjectPath } from 'object-path-immutable';
-import dayjs from 'dayjs';
+import dayjs from '@/lib/dayjs';
 import { objectHasKey } from '@/utils/helper';
 import mustacheReplacer from './mustache-replacer';
 
@@ -20,7 +20,12 @@ export const funcs = {
 
     /* eslint-disable-next-line */
     const isValidDate = date instanceof Date && !isNaN(date);
-    const result = dayjs(isValidDate ? date : Date.now()).format(dateFormat);
+    const dayjsDate = dayjs(isValidDate ? date : Date.now());
+
+    const result =
+      dateFormat === 'relative'
+        ? dayjsDate.fromNow()
+        : dayjsDate.format(dateFormat);
 
     return result;
   },
@@ -38,6 +43,7 @@ export default function ({ block, data: refData }) {
     'fileName',
     'selector',
     'prefixText',
+    'customData',
     'globalData',
     'suffixText',
     'extraRowValue',

+ 7 - 3
src/utils/shared.js

@@ -431,12 +431,16 @@ export const tasks = {
     allowedInputs: true,
     maxConnection: 1,
     data: {
+      range: '',
+      refKey: '',
+      type: 'get',
+      customData: '',
       description: '',
       spreadsheetId: '',
-      type: 'get',
-      range: '',
       firstRowAsKey: false,
-      refKey: '',
+      keysAsFirstRow: false,
+      valueInputOption: 'RAW',
+      dataFrom: 'data-columns',
     },
   },
   conditions: {