From 60491d979deb3a86e791502d9c1e8627c3e73307 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Mon, 17 Jun 2024 04:54:38 -0400 Subject: [PATCH] refactor(editor): Migrate `Credentials.store` to use composition API (no-changelog) (#9767) --- .../editor-ui/src/stores/credentials.store.ts | 696 ++++++++++-------- 1 file changed, 382 insertions(+), 314 deletions(-) diff --git a/packages/editor-ui/src/stores/credentials.store.ts b/packages/editor-ui/src/stores/credentials.store.ts index a6c5ff8c7d..94bf3b8cdc 100644 --- a/packages/editor-ui/src/stores/credentials.store.ts +++ b/packages/editor-ui/src/stores/credentials.store.ts @@ -7,20 +7,8 @@ import type { ICredentialsState, ICredentialTypeMap, } from '@/Interface'; -import { - createNewCredential, - deleteCredential, - getAllCredentials, - getAllCredentialsForWorkflow, - getCredentialData, - getCredentialsNewName, - getCredentialTypes, - oAuth1CredentialAuthorize, - oAuth2CredentialAuthorize, - testCredential, - updateCredential, -} from '@/api/credentials'; -import { setCredentialSharedWith } from '@/api/credentials.ee'; +import * as credentialsApi from '@/api/credentials'; +import * as credentialsEeApi from '@/api/credentials.ee'; import { makeRestApiRequest } from '@/utils/apiUtils'; import { getAppNameFromCredType } from '@/utils/nodeTypesUtils'; import { EnterpriseEditionFeature, STORES } from '@/constants'; @@ -38,6 +26,7 @@ import { useSettingsStore } from './settings.store'; import { isEmpty } from '@/utils/typesUtils'; import type { ProjectSharingData } from '@/types/projects.types'; import { splitName } from '@/utils/projects.utils'; +import { computed, ref } from 'vue'; const DEFAULT_CREDENTIAL_NAME = 'Unnamed credential'; const DEFAULT_CREDENTIAL_POSTFIX = 'account'; @@ -45,342 +34,421 @@ const TYPES_WITH_DEFAULT_NAME = ['httpBasicAuth', 'oAuth2Api', 'httpDigestAuth', export type CredentialsStore = ReturnType; -export const useCredentialsStore = defineStore(STORES.CREDENTIALS, { - state: (): ICredentialsState => ({ - credentialTypes: {}, - credentials: {}, - }), - getters: { - credentialTypesById(): Record { - return this.credentialTypes; - }, - allCredentialTypes(): ICredentialType[] { - return Object.values(this.credentialTypes).sort((a, b) => - a.displayName.localeCompare(b.displayName), - ); - }, - allCredentials(): ICredentialsResponse[] { - return Object.values(this.credentials).sort((a, b) => a.name.localeCompare(b.name)); - }, - allCredentialsByType(): { [type: string]: ICredentialsResponse[] } { - const credentials = this.allCredentials; - const types = this.allCredentialTypes; +export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => { + const state = ref({ credentialTypes: {}, credentials: {} }); - return types.reduce( - (accu: { [type: string]: ICredentialsResponse[] }, type: ICredentialType) => { - accu[type.name] = credentials.filter( - (cred: ICredentialsResponse) => cred.type === type.name, - ); + // --------------------------------------------------------------------------- + // #region Computed + // --------------------------------------------------------------------------- - return accu; - }, - {}, - ); - }, - allUsableCredentialsForNode() { - return (node: INodeUi): ICredentialsResponse[] => { - let credentials: ICredentialsResponse[] = []; - const nodeType = useNodeTypesStore().getNodeType(node.type, node.typeVersion); - if (nodeType?.credentials) { - nodeType.credentials.forEach((cred) => { - credentials = credentials.concat(this.allUsableCredentialsByType[cred.name]); - }); - } - return credentials.sort((a, b) => { - const aDate = new Date(a.updatedAt); - const bDate = new Date(b.updatedAt); - return aDate.getTime() - bDate.getTime(); + const credentialTypesById = computed(() => { + return state.value.credentialTypes; + }); + + const allCredentialTypes = computed(() => { + return Object.values(state.value.credentialTypes).sort((a, b) => + a.displayName.localeCompare(b.displayName), + ); + }); + + const allCredentials = computed(() => { + return Object.values(state.value.credentials).sort((a, b) => a.name.localeCompare(b.name)); + }); + + const allCredentialsByType = computed(() => { + const credentials = allCredentials.value; + const types = allCredentialTypes.value; + return types.reduce( + (accu: { [type: string]: ICredentialsResponse[] }, type: ICredentialType) => { + accu[type.name] = credentials.filter( + (cred: ICredentialsResponse) => cred.type === type.name, + ); + return accu; + }, + {}, + ); + }); + + const allUsableCredentialsByType = computed(() => { + const credentials = allCredentials.value; + const types = allCredentialTypes.value; + + return types.reduce( + (accu: { [type: string]: ICredentialsResponse[] }, type: ICredentialType) => { + accu[type.name] = credentials.filter((cred: ICredentialsResponse) => { + return cred.type === type.name; }); - }; - }, - allUsableCredentialsByType(): { [type: string]: ICredentialsResponse[] } { - const credentials = this.allCredentials; - const types = this.allCredentialTypes; - return types.reduce( - (accu: { [type: string]: ICredentialsResponse[] }, type: ICredentialType) => { - accu[type.name] = credentials.filter((cred: ICredentialsResponse) => { - return cred.type === type.name; - }); + return accu; + }, + {}, + ); + }); - return accu; - }, - {}, - ); - }, - getCredentialTypeByName() { - return (type: string): ICredentialType | undefined => this.credentialTypes[type]; - }, - getCredentialById() { - return (id: string): ICredentialsResponse => this.credentials[id]; - }, - getCredentialByIdAndType() { - return (id: string, type: string): ICredentialsResponse | undefined => { - const credential = this.credentials[id]; - return !credential || credential.type !== type ? undefined : credential; - }; - }, - getCredentialsByType() { - return (credentialType: string): ICredentialsResponse[] => { - return this.allCredentialsByType[credentialType] || []; - }; - }, - getUsableCredentialByType() { - return (credentialType: string): ICredentialsResponse[] => { - return this.allUsableCredentialsByType[credentialType] || []; - }; - }, - getNodesWithAccess() { - return (credentialTypeName: string) => { - const nodeTypesStore = useNodeTypesStore(); - const allNodeTypes: INodeTypeDescription[] = nodeTypesStore.allNodeTypes; + const allUsableCredentialsForNode = computed(() => { + return (node: INodeUi): ICredentialsResponse[] => { + let credentials: ICredentialsResponse[] = []; + const nodeType = useNodeTypesStore().getNodeType(node.type, node.typeVersion); + if (nodeType?.credentials) { + nodeType.credentials.forEach((cred) => { + credentials = credentials.concat(allUsableCredentialsByType.value[cred.name]); + }); + } + return credentials.sort((a, b) => { + const aDate = new Date(a.updatedAt); + const bDate = new Date(b.updatedAt); + return aDate.getTime() - bDate.getTime(); + }); + }; + }); - return allNodeTypes.filter((nodeType: INodeTypeDescription) => { - if (!nodeType.credentials) { - return false; - } + const getCredentialTypeByName = computed(() => { + return (type: string): ICredentialType | undefined => state.value.credentialTypes[type]; + }); - for (const credentialTypeDescription of nodeType.credentials) { - if (credentialTypeDescription.name === credentialTypeName) { - return true; - } - } + const getCredentialById = computed(() => { + return (id: string): ICredentialsResponse => state.value.credentials[id]; + }); + const getCredentialByIdAndType = computed(() => { + return (id: string, type: string): ICredentialsResponse | undefined => { + const credential = state.value.credentials[id]; + return !credential || credential.type !== type ? undefined : credential; + }; + }); + + const getCredentialsByType = computed(() => { + return (credentialType: string): ICredentialsResponse[] => { + return allCredentialsByType.value[credentialType] || []; + }; + }); + + const getUsableCredentialByType = computed(() => { + return (credentialType: string): ICredentialsResponse[] => { + return allUsableCredentialsByType.value[credentialType] || []; + }; + }); + + const getNodesWithAccess = computed(() => { + return (credentialTypeName: string) => { + const nodeTypesStore = useNodeTypesStore(); + const allNodeTypes: INodeTypeDescription[] = nodeTypesStore.allNodeTypes; + + return allNodeTypes.filter((nodeType: INodeTypeDescription) => { + if (!nodeType.credentials) { return false; - }); - }; - }, - getScopesByCredentialType() { - return (credentialTypeName: string) => { - const credentialType = this.getCredentialTypeByName(credentialTypeName); - if (!credentialType) { - return []; } - const scopeProperty = credentialType.properties.find((p) => p.name === 'scope'); - - if ( - !scopeProperty || - !scopeProperty.default || - typeof scopeProperty.default !== 'string' || - scopeProperty.default === '' - ) { - return []; + for (const credentialTypeDescription of nodeType.credentials) { + if (credentialTypeDescription.name === credentialTypeName) { + return true; + } } - let { default: scopeDefault } = scopeProperty; + return false; + }); + }; + }); - // disregard expressions for display - scopeDefault = scopeDefault.replace(/^=/, '').replace(/\{\{.*\}\}/, ''); + const getScopesByCredentialType = computed(() => { + return (credentialTypeName: string) => { + const credentialType = getCredentialTypeByName.value(credentialTypeName); + if (!credentialType) { + return []; + } - if (/ /.test(scopeDefault)) return scopeDefault.split(' '); + const scopeProperty = credentialType.properties.find((p) => p.name === 'scope'); - if (/,/.test(scopeDefault)) return scopeDefault.split(','); + if ( + !scopeProperty || + !scopeProperty.default || + typeof scopeProperty.default !== 'string' || + scopeProperty.default === '' + ) { + return []; + } - return [scopeDefault]; - }; - }, - getCredentialOwnerName() { - return (credential: ICredentialsResponse | IUsedCredential | undefined): string => { - const { firstName, lastName, email } = splitName(credential?.homeProject?.name ?? ''); + let { default: scopeDefault } = scopeProperty; - return credential?.homeProject?.name - ? `${firstName} ${lastName} (${email})` - : i18n.baseText('credentialEdit.credentialSharing.info.sharee.fallback'); - }; - }, - getCredentialOwnerNameById() { - return (credentialId: string): string => { - const credential = this.getCredentialById(credentialId); + // disregard expressions for display + scopeDefault = scopeDefault.replace(/^=/, '').replace(/\{\{.*\}\}/, ''); - return this.getCredentialOwnerName(credential); - }; - }, - httpOnlyCredentialTypes(): ICredentialType[] { - return this.allCredentialTypes.filter((credentialType) => credentialType.httpRequestNode); - }, - }, - actions: { - setCredentialTypes(credentialTypes: ICredentialType[]): void { - this.credentialTypes = credentialTypes.reduce( - (accu: ICredentialTypeMap, cred: ICredentialType) => { - accu[cred.name] = cred; + if (/ /.test(scopeDefault)) return scopeDefault.split(' '); - return accu; - }, - {}, - ); - }, - setCredentials(credentials: ICredentialsResponse[]): void { - this.credentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => { + if (/,/.test(scopeDefault)) return scopeDefault.split(','); + + return [scopeDefault]; + }; + }); + + const getCredentialOwnerName = computed(() => { + return (credential: ICredentialsResponse | IUsedCredential | undefined): string => { + const { firstName, lastName, email } = splitName(credential?.homeProject?.name ?? ''); + + return credential?.homeProject?.name + ? `${firstName} ${lastName} (${email})` + : i18n.baseText('credentialEdit.credentialSharing.info.sharee.fallback'); + }; + }); + + const getCredentialOwnerNameById = computed(() => { + return (credentialId: string): string => { + const credential = getCredentialById.value(credentialId); + + return getCredentialOwnerName.value(credential); + }; + }); + + const httpOnlyCredentialTypes = computed(() => { + return allCredentialTypes.value.filter((credentialType) => credentialType.httpRequestNode); + }); + + // #endregion + + // --------------------------------------------------------------------------- + // #region Methods + // --------------------------------------------------------------------------- + + const setCredentialTypes = (credentialTypes: ICredentialType[]) => { + state.value.credentialTypes = credentialTypes.reduce( + (accu: ICredentialTypeMap, cred: ICredentialType) => { + accu[cred.name] = cred; + + return accu; + }, + {}, + ); + }; + + const addCredentials = (credentials: ICredentialsResponse[]) => { + credentials.forEach((cred: ICredentialsResponse) => { + if (cred.id) { + state.value.credentials[cred.id] = { ...state.value.credentials[cred.id], ...cred }; + } + }); + }; + + const setCredentials = (credentials: ICredentialsResponse[]) => { + state.value.credentials = credentials.reduce( + (accu: ICredentialMap, cred: ICredentialsResponse) => { if (cred.id) { accu[cred.id] = cred; } return accu; - }, {}); - }, - addCredentials(credentials: ICredentialsResponse[]): void { - credentials.forEach((cred: ICredentialsResponse) => { - if (cred.id) { - this.credentials[cred.id] = { ...this.credentials[cred.id], ...cred }; - } - }); - }, - upsertCredential(credential: ICredentialsResponse): void { - if (credential.id) { - this.credentials = { - ...this.credentials, - [credential.id]: { - ...this.credentials[credential.id], - ...credential, - }, - }; - } - }, - async fetchCredentialTypes(forceFetch: boolean): Promise { - if (this.allCredentialTypes.length > 0 && !forceFetch) { - return; - } - const rootStore = useRootStore(); - const credentialTypes = await getCredentialTypes(rootStore.getBaseUrl); - this.setCredentialTypes(credentialTypes); - }, - async fetchAllCredentials( - projectId?: string, - includeScopes = true, - ): Promise { - const rootStore = useRootStore(); + }, + {}, + ); + }; - const filter = { - projectId, + const upsertCredential = (credential: ICredentialsResponse) => { + if (credential.id) { + state.value.credentials = { + ...state.value.credentials, + [credential.id]: { + ...state.value.credentials[credential.id], + ...credential, + }, }; + } + }; - const credentials = await getAllCredentials( - rootStore.getRestApiContext, - isEmpty(filter) ? undefined : filter, - includeScopes, - ); - this.setCredentials(credentials); - return credentials; - }, - async fetchAllCredentialsForWorkflow( - options: { workflowId: string } | { projectId: string }, - ): Promise { - const rootStore = useRootStore(); + const fetchCredentialTypes = async (forceFetch: boolean) => { + if (allCredentialTypes.value.length > 0 && !forceFetch) { + return; + } + const rootStore = useRootStore(); + const credentialTypes = await credentialsApi.getCredentialTypes(rootStore.getBaseUrl); + setCredentialTypes(credentialTypes); + }; - const credentials = await getAllCredentialsForWorkflow(rootStore.getRestApiContext, options); - this.setCredentials(credentials); - return credentials; - }, - async getCredentialData({ - id, - }: { - id: string; - }): Promise { - const rootStore = useRootStore(); - return await getCredentialData(rootStore.getRestApiContext, id); - }, - async createNewCredential( - data: ICredentialsDecrypted, - projectId?: string, - ): Promise { - const rootStore = useRootStore(); - const settingsStore = useSettingsStore(); - const credential = await createNewCredential(rootStore.getRestApiContext, data, projectId); + const fetchAllCredentials = async ( + projectId?: string, + includeScopes = true, + ): Promise => { + const rootStore = useRootStore(); - if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) { - this.upsertCredential(credential); - if (data.sharedWithProjects) { - await this.setCredentialSharedWith({ - credentialId: credential.id, - sharedWithProjects: data.sharedWithProjects, - }); - } - } else { - this.upsertCredential(credential); - } - return credential; - }, - async updateCredential(params: { - data: ICredentialsDecrypted; - id: string; - }): Promise { - const { id, data } = params; - const rootStore = useRootStore(); - const credential = await updateCredential(rootStore.getRestApiContext, id, data); + const filter = { + projectId, + }; - this.upsertCredential(credential); + const credentials = await credentialsApi.getAllCredentials( + rootStore.getRestApiContext, + isEmpty(filter) ? undefined : filter, + includeScopes, + ); + setCredentials(credentials); + return credentials; + }; - return credential; - }, - async deleteCredential({ id }: { id: string }) { - const rootStore = useRootStore(); - const deleted = await deleteCredential(rootStore.getRestApiContext, id); - if (deleted) { - const { [id]: deletedCredential, ...rest } = this.credentials; - this.credentials = rest; - } - }, - async oAuth2Authorize(data: ICredentialsResponse): Promise { - const rootStore = useRootStore(); - return await oAuth2CredentialAuthorize(rootStore.getRestApiContext, data); - }, - async oAuth1Authorize(data: ICredentialsResponse): Promise { - const rootStore = useRootStore(); - return await oAuth1CredentialAuthorize(rootStore.getRestApiContext, data); - }, - async testCredential(data: ICredentialsDecrypted): Promise { - const rootStore = useRootStore(); - return await testCredential(rootStore.getRestApiContext, { credentials: data }); - }, - async getNewCredentialName(params: { credentialTypeName: string }): Promise { - try { - const { credentialTypeName } = params; - let newName = DEFAULT_CREDENTIAL_NAME; - if (!TYPES_WITH_DEFAULT_NAME.includes(credentialTypeName)) { - const cred = this.getCredentialTypeByName(credentialTypeName); - newName = cred ? getAppNameFromCredType(cred.displayName) : ''; - newName = - newName.length > 0 - ? `${newName} ${DEFAULT_CREDENTIAL_POSTFIX}` - : DEFAULT_CREDENTIAL_NAME; - } - const rootStore = useRootStore(); - const res = await getCredentialsNewName(rootStore.getRestApiContext, newName); - return res.name; - } catch (e) { - return DEFAULT_CREDENTIAL_NAME; - } - }, - async setCredentialSharedWith(payload: { - sharedWithProjects: ProjectSharingData[]; - credentialId: string; - }): Promise { - if (useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) { - await setCredentialSharedWith(useRootStore().getRestApiContext, payload.credentialId, { - shareWithIds: payload.sharedWithProjects.map((project) => project.id), + const fetchAllCredentialsForWorkflow = async ( + options: { workflowId: string } | { projectId: string }, + ): Promise => { + const rootStore = useRootStore(); + + const credentials = await credentialsApi.getAllCredentialsForWorkflow( + rootStore.getRestApiContext, + options, + ); + setCredentials(credentials); + return credentials; + }; + + const getCredentialData = async ({ + id, + }: { + id: string; + }): Promise => { + const rootStore = useRootStore(); + return await credentialsApi.getCredentialData(rootStore.getRestApiContext, id); + }; + + const createNewCredential = async ( + data: ICredentialsDecrypted, + projectId?: string, + ): Promise => { + const rootStore = useRootStore(); + const settingsStore = useSettingsStore(); + const credential = await credentialsApi.createNewCredential( + rootStore.getRestApiContext, + data, + projectId, + ); + + if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) { + upsertCredential(credential); + if (data.sharedWithProjects) { + await setCredentialSharedWith({ + credentialId: credential.id, + sharedWithProjects: data.sharedWithProjects, }); - - this.credentials[payload.credentialId] = { - ...this.credentials[payload.credentialId], - sharedWithProjects: payload.sharedWithProjects, - }; } - return this.credentials[payload.credentialId]; - }, + } else { + upsertCredential(credential); + } + return credential; + }; - async getCredentialTranslation(credentialType: string): Promise { + const updateCredential = async (params: { + data: ICredentialsDecrypted; + id: string; + }): Promise => { + const { id, data } = params; + const rootStore = useRootStore(); + const credential = await credentialsApi.updateCredential(rootStore.getRestApiContext, id, data); + + upsertCredential(credential); + + return credential; + }; + + const deleteCredential = async ({ id }: { id: string }) => { + const rootStore = useRootStore(); + const deleted = await credentialsApi.deleteCredential(rootStore.getRestApiContext, id); + if (deleted) { + const { [id]: deletedCredential, ...rest } = state.value.credentials; + state.value.credentials = rest; + } + }; + + const oAuth2Authorize = async (data: ICredentialsResponse): Promise => { + const rootStore = useRootStore(); + return await credentialsApi.oAuth2CredentialAuthorize(rootStore.getRestApiContext, data); + }; + + const oAuth1Authorize = async (data: ICredentialsResponse): Promise => { + const rootStore = useRootStore(); + return await credentialsApi.oAuth1CredentialAuthorize(rootStore.getRestApiContext, data); + }; + + const testCredential = async ( + data: ICredentialsDecrypted, + ): Promise => { + const rootStore = useRootStore(); + return await credentialsApi.testCredential(rootStore.getRestApiContext, { credentials: data }); + }; + + const getNewCredentialName = async (params: { credentialTypeName: string }): Promise => { + try { + const { credentialTypeName } = params; + let newName = DEFAULT_CREDENTIAL_NAME; + if (!TYPES_WITH_DEFAULT_NAME.includes(credentialTypeName)) { + const cred = getCredentialTypeByName.value(credentialTypeName); + newName = cred ? getAppNameFromCredType(cred.displayName) : ''; + newName = + newName.length > 0 ? `${newName} ${DEFAULT_CREDENTIAL_POSTFIX}` : DEFAULT_CREDENTIAL_NAME; + } const rootStore = useRootStore(); - return await makeRestApiRequest( - rootStore.getRestApiContext, - 'GET', - '/credential-translation', + const res = await credentialsApi.getCredentialsNewName(rootStore.getRestApiContext, newName); + return res.name; + } catch (e) { + return DEFAULT_CREDENTIAL_NAME; + } + }; + + const setCredentialSharedWith = async (payload: { + sharedWithProjects: ProjectSharingData[]; + credentialId: string; + }): Promise => { + if (useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) { + await credentialsEeApi.setCredentialSharedWith( + useRootStore().getRestApiContext, + payload.credentialId, { - credentialType, + shareWithIds: payload.sharedWithProjects.map((project) => project.id), }, ); - }, - }, + + state.value.credentials[payload.credentialId] = { + ...state.value.credentials[payload.credentialId], + sharedWithProjects: payload.sharedWithProjects, + }; + } + return state.value.credentials[payload.credentialId]; + }; + + const getCredentialTranslation = async (credentialType: string): Promise => { + const rootStore = useRootStore(); + return await makeRestApiRequest(rootStore.getRestApiContext, 'GET', '/credential-translation', { + credentialType, + }); + }; + + // #endregion + + return { + getCredentialOwnerName, + getCredentialsByType, + getCredentialById, + getCredentialTypeByName, + getCredentialByIdAndType, + getNodesWithAccess, + getUsableCredentialByType, + credentialTypesById, + httpOnlyCredentialTypes, + getScopesByCredentialType, + getCredentialOwnerNameById, + allUsableCredentialsForNode, + allCredentials, + allCredentialTypes, + allUsableCredentialsByType, + setCredentialTypes, + addCredentials, + setCredentials, + deleteCredential, + upsertCredential, + fetchCredentialTypes, + fetchAllCredentials, + fetchAllCredentialsForWorkflow, + createNewCredential, + updateCredential, + getCredentialData, + oAuth1Authorize, + oAuth2Authorize, + getNewCredentialName, + testCredential, + getCredentialTranslation, + setCredentialSharedWith, + }; }); /** @@ -404,12 +472,12 @@ export const listenForCredentialChanges = (opts: { switch (name) { case 'createNewCredential': - const createdCredential = returnValue as ICredentialsResponse; + const createdCredential = returnValue as unknown as ICredentialsResponse; onCredentialCreated?.(createdCredential); break; case 'updateCredential': - const updatedCredential = returnValue as ICredentialsResponse; + const updatedCredential = returnValue as unknown as ICredentialsResponse; onCredentialUpdated?.(updatedCredential); break;