1
0
Эх сурвалжийг харах

feat: multiple teams workflows

Ahmad Kholid 2 жил өмнө
parent
commit
55f27035ff

+ 39 - 16
src/components/newtab/workflow/WorkflowShareTeam.vue

@@ -9,13 +9,29 @@
     <p v-else class="font-semibold">Update workflow</p>
     <p v-else class="font-semibold">Update workflow</p>
     <div class="flex items-start mt-4">
     <div class="flex items-start mt-4">
       <div class="flex-1 mr-8">
       <div class="flex-1 mr-8">
-        <ui-input
-          v-model="state.workflow.name"
-          :label="t('workflow.name')"
-          type="text"
-          name="workflow name"
-          class="w-full"
-        />
+        <div class="flex items-center">
+          <ui-input
+            v-model="state.workflow.name"
+            :label="t('workflow.name')"
+            type="text"
+            name="workflow name"
+            class="flex-1"
+          />
+          <ui-select
+            v-if="!isUpdate"
+            v-model="state.activeTeam"
+            label="Select team"
+            class="ml-4"
+          >
+            <option
+              v-for="team in state.userTeams"
+              :key="team.id"
+              :value="team.id"
+            >
+              {{ team.name }}
+            </option>
+          </ui-select>
+        </div>
         <div class="relative mb-2 mt-2">
         <div class="relative mb-2 mt-2">
           <label
           <label
             for="short-description"
             for="short-description"
@@ -60,7 +76,7 @@
             class="w-full"
             class="w-full"
             @click="$emit('update', state.workflow)"
             @click="$emit('update', state.workflow)"
           >
           >
-            Update
+            Save
           </ui-button>
           </ui-button>
           <ui-button class="w-full mt-2" @click="$emit('close')">
           <ui-button class="w-full mt-2" @click="$emit('close')">
             {{ t('common.cancel') }}
             {{ t('common.cancel') }}
@@ -124,6 +140,7 @@ import { reactive, watch, onMounted } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { useI18n } from 'vue-i18n';
 import { useToast } from 'vue-toastification';
 import { useToast } from 'vue-toastification';
 import browser from 'webextension-polyfill';
 import browser from 'webextension-polyfill';
+import cloneDeep from 'lodash.clonedeep';
 import { fetchApi } from '@/utils/api';
 import { fetchApi } from '@/utils/api';
 import { useUserStore } from '@/stores/user';
 import { useUserStore } from '@/stores/user';
 import { useTeamWorkflowStore } from '@/stores/teamWorkflow';
 import { useTeamWorkflowStore } from '@/stores/teamWorkflow';
@@ -147,6 +164,8 @@ const userStore = useUserStore();
 const teamWorkflowStore = useTeamWorkflowStore();
 const teamWorkflowStore = useTeamWorkflowStore();
 
 
 const state = reactive({
 const state = reactive({
+  userTeams: [],
+  activeTeam: null,
   contentLength: 0,
   contentLength: 0,
   isPublishing: false,
   isPublishing: false,
   workflow: JSON.parse(JSON.stringify(props.workflow)),
   workflow: JSON.parse(JSON.stringify(props.workflow)),
@@ -165,13 +184,10 @@ async function publishWorkflow() {
 
 
     delete workflow.extVersion;
     delete workflow.extVersion;
 
 
-    const response = await fetchApi(
-      `/teams/${userStore.user.team.id}/workflows`,
-      {
-        method: 'POST',
-        body: JSON.stringify({ workflow }),
-      }
-    );
+    const response = await fetchApi(`/teams/${state.activeTeam}/workflows`, {
+      method: 'POST',
+      body: JSON.stringify({ workflow }),
+    });
     const result = await response.json();
     const result = await response.json();
 
 
     if (!response.ok) {
     if (!response.ok) {
@@ -181,9 +197,11 @@ async function publishWorkflow() {
       throw error;
       throw error;
     }
     }
 
 
+    workflow.id = result.id;
+    workflow.createdAt = Date.now();
     workflow.drawflow = props.workflow.drawflow;
     workflow.drawflow = props.workflow.drawflow;
 
 
-    teamWorkflowStore.insert(workflow);
+    await teamWorkflowStore.insert(state.activeTeam, cloneDeep(workflow));
     state.isPublishing = false;
     state.isPublishing = false;
 
 
     emit('publish');
     emit('publish');
@@ -231,6 +249,11 @@ onMounted(() => {
         state.workflow.tag = 'stage';
         state.workflow.tag = 'stage';
       }
       }
     });
     });
+
+    state.userTeams = userStore.user.teams.filter((team) =>
+      team.access.some((item) => ['owner', 'create'].includes(item))
+    );
+    if (state.userTeams[0]) state.activeTeam = state.userTeams[0].id;
   }
   }
 });
 });
 </script>
 </script>

+ 20 - 10
src/components/newtab/workflow/editor/EditorLocalActions.vue

@@ -66,13 +66,13 @@
         </transition-expand>
         </transition-expand>
       </div>
       </div>
     </ui-popover>
     </ui-popover>
-    <ui-popover :disabled="userDontHaveTeamAccess">
+    <ui-popover :disabled="userDontHaveTeamsAccess">
       <template #trigger>
       <template #trigger>
         <button
         <button
           v-tooltip.group="t('workflow.share.title')"
           v-tooltip.group="t('workflow.share.title')"
           :class="{ 'text-primary': shared }"
           :class="{ 'text-primary': shared }"
           class="hoverable p-2 rounded-lg"
           class="hoverable p-2 rounded-lg"
-          @click="shareWorkflow(!userDontHaveTeamAccess)"
+          @click="shareWorkflow(!userDontHaveTeamsAccess)"
         >
         >
           <v-remixicon name="riShareLine" />
           <v-remixicon name="riShareLine" />
         </button>
         </button>
@@ -355,6 +355,8 @@ const shortcuts = useShortcut([
   getShortcut('editor:execute-workflow', executeWorkflow),
   getShortcut('editor:execute-workflow', executeWorkflow),
 ]);
 ]);
 
 
+const { teamId } = router.currentRoute.value.params;
+
 const state = reactive({
 const state = reactive({
   triggerText: '',
   triggerText: '',
   loadingSync: false,
   loadingSync: false,
@@ -371,9 +373,13 @@ const renameState = reactive({
 
 
 const shared = computed(() => sharedWorkflowStore.getById(props.workflow.id));
 const shared = computed(() => sharedWorkflowStore.getById(props.workflow.id));
 const hosted = computed(() => userStore.hostedWorkflows[props.workflow.id]);
 const hosted = computed(() => userStore.hostedWorkflows[props.workflow.id]);
-const userDontHaveTeamAccess = computed(
-  () => !userStore.validateTeamAccess(['owner', 'create'])
-);
+const userDontHaveTeamsAccess = computed(() => {
+  if (props.isTeam || !userStore.user.teams) return false;
+
+  return !userStore.user.teams.some((team) =>
+    team.access.some((item) => ['owner', 'create'].includes(item))
+  );
+});
 
 
 function updateWorkflow(data = {}, changedIndicator = false) {
 function updateWorkflow(data = {}, changedIndicator = false) {
   let store = null;
   let store = null;
@@ -381,8 +387,8 @@ function updateWorkflow(data = {}, changedIndicator = false) {
   if (props.isTeam) {
   if (props.isTeam) {
     store = teamWorkflow.update({
     store = teamWorkflow.update({
       data,
       data,
+      teamId,
       id: props.workflow.id,
       id: props.workflow.id,
-      teamId: router.currentRoute.value.params.teamId,
     });
     });
   } else {
   } else {
     store = workflowStore.update({
     store = workflowStore.update({
@@ -512,7 +518,11 @@ function clearRenameModal() {
 async function publishWorkflow() {
 async function publishWorkflow() {
   if (!props.canEdit) return;
   if (!props.canEdit) return;
 
 
-  const workflowPaylod = convertWorkflow(props.workflow, ['id']);
+  const workflowPaylod = convertWorkflow(props.workflow, [
+    'id',
+    'tag',
+    'content',
+  ]);
   workflowPaylod.drawflow = parseJSON(
   workflowPaylod.drawflow = parseJSON(
     props.workflow.drawflow,
     props.workflow.drawflow,
     props.workflow.drawflow
     props.workflow.drawflow
@@ -524,7 +534,7 @@ async function publishWorkflow() {
 
 
   try {
   try {
     const response = await fetchApi(
     const response = await fetchApi(
-      `/teams/${userStore.user.team.id}/workflows/${props.workflow.id}`,
+      `/teams/${teamId}/workflows/${props.workflow.id}`,
       {
       {
         method: 'PATCH',
         method: 'PATCH',
         body: JSON.stringify({ workflow: workflowPaylod }),
         body: JSON.stringify({ workflow: workflowPaylod }),
@@ -624,7 +634,7 @@ async function syncWorkflow() {
 
 
   try {
   try {
     const response = await fetchApi(
     const response = await fetchApi(
-      `/teams/${userStore.user.team.id}/workflows/${props.workflow.id}`
+      `/teams/${teamId}/workflows/${props.workflow.id}`
     );
     );
     const result = await response.json();
     const result = await response.json();
 
 
@@ -633,9 +643,9 @@ async function syncWorkflow() {
     }
     }
 
 
     await teamWorkflow.update({
     await teamWorkflow.update({
+      teamId,
       data: result,
       data: result,
       id: props.workflow.id,
       id: props.workflow.id,
-      teamId: router.currentRoute.value.params.teamId,
     });
     });
 
 
     const convertedData = convertWorkflowData(result);
     const convertedData = convertWorkflowData(result);

+ 7 - 5
src/components/newtab/workflows/WorkflowsUserTeam.vue

@@ -16,7 +16,7 @@
       Browse workflows that been shared by your team
       Browse workflows that been shared by your team
     </p>
     </p>
     <ui-button
     <ui-button
-      :href="`http://localhost:3002/workflows?teamId=${userStore.user.team.id}&workflowsBy=team`"
+      :href="`http://localhost:3002/workflows?teamId=${teamId}&workflowsBy=team`"
       tag="a"
       tag="a"
       target="_blank"
       target="_blank"
       variant="accent"
       variant="accent"
@@ -33,7 +33,7 @@
       :menu="menu"
       :menu="menu"
       @menuSelected="onMenuSelected"
       @menuSelected="onMenuSelected"
       @execute="executeWorkflow(workflow)"
       @execute="executeWorkflow(workflow)"
-      @click="$router.push(`/teams/${workflow.teamId}/workflows/${$event.id}`)"
+      @click="$router.push(`/teams/${teamId}/workflows/${$event.id}`)"
     >
     >
       <template #footer-content>
       <template #footer-content>
         <span
         <span
@@ -62,6 +62,10 @@ const props = defineProps({
     type: String,
     type: String,
     default: '',
     default: '',
   },
   },
+  teamId: {
+    type: [String, Number],
+    default: '',
+  },
   sort: {
   sort: {
     type: Object,
     type: Object,
     default: () => ({
     default: () => ({
@@ -85,9 +89,7 @@ const dialog = useDialog();
 const userStore = useUserStore();
 const userStore = useUserStore();
 const teamWorkflowStore = useTeamWorkflowStore();
 const teamWorkflowStore = useTeamWorkflowStore();
 
 
-const teamWorkflows = computed(() =>
-  teamWorkflowStore.getByTeam(userStore.user?.team?.id)
-);
+const teamWorkflows = computed(() => teamWorkflowStore.getByTeam(props.teamId));
 const workflows = computed(() => {
 const workflows = computed(() => {
   const filtered = teamWorkflows.value.filter(({ name }) =>
   const filtered = teamWorkflows.value.filter(({ name }) =>
     name.toLocaleLowerCase().includes(props.search.toLocaleLowerCase())
     name.toLocaleLowerCase().includes(props.search.toLocaleLowerCase())

+ 3 - 1
src/content/services/recordWorkflow/App.vue

@@ -458,7 +458,9 @@ onMounted(() => {
   browser.storage.local
   browser.storage.local
     .get(['recording', 'workflows'])
     .get(['recording', 'workflows'])
     .then(({ recording, workflows }) => {
     .then(({ recording, workflows }) => {
-      const workflow = workflows.find(({ id }) => recording.workflowId === id);
+      const workflow = Object.values(workflows).find(
+        ({ id }) => recording.workflowId === id
+      );
 
 
       addBlockState.workflowColumns = workflow?.table || [];
       addBlockState.workflowColumns = workflow?.table || [];
     });
     });

+ 50 - 14
src/newtab/pages/Workflows.vue

@@ -49,16 +49,32 @@
               {{ t('workflow.browse') }}
               {{ t('workflow.browse') }}
             </span>
             </span>
           </ui-list-item>
           </ui-list-item>
-          <ui-list-item
-            v-if="userTeamWorkflows.length > 0 || userStore.user?.team"
-            :active="state.activeTab === 'team-workflows'"
-            color="bg-box-transparent font-semibold"
-            class="cursor-pointer"
-            @click="state.activeTab = 'team-workflows'"
+          <ui-expand
+            v-if="userTeamWorkflows.length > 0 || userStore.user?.teams"
+            append-icon
+            header-class="px-4 py-2 rounded-lg mb-1 hoverable w-full flex items-center"
           >
           >
-            <v-remixicon name="riTeamLine" />
-            <span class="ml-4"> Team Workflows </span>
-          </ui-list-item>
+            <template #header>
+              <v-remixicon name="riTeamLine" />
+              <span class="ml-4 capitalize flex-1 text-left">
+                Team Workflows
+              </span>
+            </template>
+            <ui-list class="space-y-1">
+              <ui-list-item
+                v-for="team in userStore.user.teams"
+                :key="team.id"
+                :active="state.teamId === team.id || +state.teamId === team.id"
+                color="bg-box-transparent font-semibold"
+                class="pl-14 cursor-pointer"
+                @click="updateActiveTab({ activeTab: 'team', teamId: team.id })"
+              >
+                <span class="text-overflow">
+                  {{ team.name }}
+                </span>
+              </ui-list-item>
+            </ui-list>
+          </ui-expand>
           <ui-expand
           <ui-expand
             :model-value="true"
             :model-value="true"
             append-icon
             append-icon
@@ -76,7 +92,7 @@
                 :active="state.activeTab === 'local'"
                 :active="state.activeTab === 'local'"
                 color="bg-box-transparent font-semibold"
                 color="bg-box-transparent font-semibold"
                 class="pl-14"
                 class="pl-14"
-                @click="state.activeTab = 'local'"
+                @click="updateActiveTab({ activeTab: 'local' })"
               >
               >
                 <span class="capitalize">
                 <span class="capitalize">
                   {{ t('workflow.type.local') }}
                   {{ t('workflow.type.local') }}
@@ -88,7 +104,7 @@
                 tag="button"
                 tag="button"
                 color="bg-box-transparent font-semibold"
                 color="bg-box-transparent font-semibold"
                 class="pl-14"
                 class="pl-14"
-                @click="state.activeTab = 'shared'"
+                @click="updateActiveTab({ activeTab: 'shared' })"
               >
               >
                 <span class="capitalize">
                 <span class="capitalize">
                   {{ t('workflow.type.shared') }}
                   {{ t('workflow.type.shared') }}
@@ -100,7 +116,7 @@
                 color="bg-box-transparent font-semibold"
                 color="bg-box-transparent font-semibold"
                 tag="button"
                 tag="button"
                 class="pl-14"
                 class="pl-14"
-                @click="state.activeTab = 'host'"
+                @click="updateActiveTab({ activeTab: 'host' })"
               >
               >
                 <span class="capitalize">
                 <span class="capitalize">
                   {{ t('workflow.type.host') }}
                   {{ t('workflow.type.host') }}
@@ -154,8 +170,9 @@
           </div>
           </div>
         </div>
         </div>
         <ui-tab-panels v-model="state.activeTab" class="flex-1 mt-6">
         <ui-tab-panels v-model="state.activeTab" class="flex-1 mt-6">
-          <ui-tab-panel value="team-workflows">
+          <ui-tab-panel value="team">
             <workflows-user-team
             <workflows-user-team
+              :team-id="state.teamId"
               :search="state.query"
               :search="state.query"
               :sort="{ by: state.sortBy, order: state.sortOrder }"
               :sort="{ by: state.sortBy, order: state.sortOrder }"
             />
             />
@@ -219,6 +236,7 @@
 <script setup>
 <script setup>
 import { computed, shallowReactive, watch } from 'vue';
 import { computed, shallowReactive, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { useI18n } from 'vue-i18n';
+import { useRouter } from 'vue-router';
 import { useToast } from 'vue-toastification';
 import { useToast } from 'vue-toastification';
 import { useDialog } from '@/composable/dialog';
 import { useDialog } from '@/composable/dialog';
 import { useShortcut } from '@/composable/shortcut';
 import { useShortcut } from '@/composable/shortcut';
@@ -240,18 +258,21 @@ useGroupTooltip();
 const { t } = useI18n();
 const { t } = useI18n();
 const toast = useToast();
 const toast = useToast();
 const dialog = useDialog();
 const dialog = useDialog();
+const router = useRouter();
 const userStore = useUserStore();
 const userStore = useUserStore();
 const workflowStore = useWorkflowStore();
 const workflowStore = useWorkflowStore();
 const teamWorkflowStore = useTeamWorkflowStore();
 const teamWorkflowStore = useTeamWorkflowStore();
 const hostedWorkflowStore = useHostedWorkflowStore();
 const hostedWorkflowStore = useHostedWorkflowStore();
 
 
 const sorts = ['name', 'createdAt'];
 const sorts = ['name', 'createdAt'];
+const { teamId, active } = router.currentRoute.value.query;
 
 
 const savedSorts = JSON.parse(localStorage.getItem('workflow-sorts') || '{}');
 const savedSorts = JSON.parse(localStorage.getItem('workflow-sorts') || '{}');
 const state = shallowReactive({
 const state = shallowReactive({
   query: '',
   query: '',
   activeFolder: '',
   activeFolder: '',
-  activeTab: 'local',
+  teamId: teamId || '',
+  activeTab: active || 'local',
   perPage: savedSorts.perPage || 18,
   perPage: savedSorts.perPage || 18,
   sortBy: savedSorts.sortBy || 'createdAt',
   sortBy: savedSorts.sortBy || 'createdAt',
   sortOrder: savedSorts.sortOrder || 'desc',
   sortOrder: savedSorts.sortOrder || 'desc',
@@ -276,6 +297,11 @@ function clearAddWorkflowModal() {
     description: '',
     description: '',
   });
   });
 }
 }
+function updateActiveTab(data = {}) {
+  if (data.activeTab !== 'team') data.teamId = '';
+
+  Object.assign(state, data);
+}
 function addWorkflow() {
 function addWorkflow() {
   workflowStore.insert({
   workflowStore.insert({
     name: addWorkflowModal.name,
     name: addWorkflowModal.name,
@@ -355,6 +381,16 @@ watch(
     );
     );
   }
   }
 );
 );
+watch(
+  () => [state.activeTab, state.teamId],
+  ([activeTab, teamIdQuery]) => {
+    const query = { active: activeTab };
+
+    if (teamIdQuery) query.teamId = teamIdQuery;
+
+    router.replace({ ...router.currentRoute.value, query });
+  }
+);
 </script>
 </script>
 <style>
 <style>
 .workflow-sort select {
 .workflow-sort select {

+ 1 - 1
src/newtab/pages/workflows/[id].vue

@@ -378,7 +378,7 @@ const workflowModals = {
 const haveEditAccess = computed(() => {
 const haveEditAccess = computed(() => {
   if (!isTeamWorkflow) return true;
   if (!isTeamWorkflow) return true;
 
 
-  return userStore.validateTeamAccess(['edit', 'owner', 'create']);
+  return userStore.validateTeamAccess(teamId, ['edit', 'owner', 'create']);
 });
 });
 const workflow = computed(() => {
 const workflow = computed(() => {
   if (isTeamWorkflow) {
   if (isTeamWorkflow) {

+ 1 - 1
src/stores/teamWorkflow.js

@@ -32,7 +32,7 @@ export const useTeamWorkflowStore = defineStore('team-workflows', {
           this.workflows[teamId][item.id] = item;
           this.workflows[teamId][item.id] = item;
         });
         });
       } else {
       } else {
-        this.workflows[data.id] = data;
+        this.workflows[teamId][data.id] = data;
       }
       }
 
 
       await this.saveToStorage('workflows');
       await this.saveToStorage('workflows');

+ 6 - 3
src/stores/user.js

@@ -13,10 +13,13 @@ export const useUserStore = defineStore('user', {
     getHostedWorkflows: (state) => Object.values(state.hostedWorkflows),
     getHostedWorkflows: (state) => Object.values(state.hostedWorkflows),
     validateTeamAccess:
     validateTeamAccess:
       (state) =>
       (state) =>
-      (access = []) => {
-        if (!state.user?.team) return false;
+      (teamId, access = []) => {
+        const currentTeam = state.user?.teams?.find(
+          ({ id }) => teamId === id || +teamId === id
+        );
+        if (!currentTeam) return false;
 
 
-        return access.some((item) => state.user.team.access.includes(item));
+        return access.some((item) => currentTeam.access.includes(item));
       },
       },
   },
   },
   actions: {
   actions: {

+ 1 - 1
webpack.config.js

@@ -112,7 +112,7 @@ const options = {
         type: 'asset/resource',
         type: 'asset/resource',
         dependency: { not: [/node_modules/] },
         dependency: { not: [/node_modules/] },
         generator: {
         generator: {
-          filename: '[name].[ext]',
+          filename: '[name][ext]',
         },
         },
       },
       },
       {
       {