refactor(editor): Finish pinia migration, remove all vuex dependancies (#4533)
* ✨ Added pinia support. Migrated community nodes module. * ✨ Added ui pinia store, moved some data from root store to it, updated modals to work with pinia stores * ✨ Added ui pinia store and migrated a part of the root store * ✨ Migrated `settings` store to pinia * ✨ Removing vuex store refs from router * ✨ Migrated `users` module to pinia store * ⚡ Fixing errors after sync with master * ⚡ One more error after merge * ⚡ Created `workflows` pinia store. Moved large part of root store to it. Started updating references. * ✨ Finished migrating workflows store to pinia * ⚡ Renaming some getters and actions to make more sense * ✨ Finished migrating the root store to pinia * ✨ Migrated ndv store to pinia * ⚡ Renaming main panel dimensions getter so it doesn't clash with data prop name * ✔️ Fixing lint errors * ✨ Migrated `templates` store to pinia * ✨ Migrated the `nodeTypes`store * ⚡ Removed unused pieces of code and oold vuex modules * ✨ Adding vuex calls to pinia store, fixing wrong references * 💄 Removing leftover $store refs * ⚡ Added legacy getters and mutations to store to support webhooks * ⚡ Added missing front-end hooks, updated vuex state subscriptions to pinia * ✔️ Fixing linting errors * ⚡ Removing vue composition api plugin * ⚡ Fixing main sidebar state when loading node view * 🐛 Fixing an error when activating workflows * 🐛 Fixing isses with workflow settings and executions auto-refresh * 🐛 Removing duplicate listeners which cause import error * 🐛 Fixing route authentication * ⚡ Updating freshly pulled $store refs * ⚡ Adding deleted const * ⚡ Updating store references in ee features. Reseting NodeView credentials update flag when resetting workspace * ⚡ Adding return type to email submission modal * ⚡ Making NodeView only react to paste event when active * 🐛 Fixing signup view errors * ✨ Started migrating the `credentials` module to pinia * 👌 Addressing PR review comments * ✨ Migrated permissions module to pinia * ✨ Migrated `nodeCreator`, `tags` and `versions` modules to pinia * ✨ Implemented webhooks pinia store * ⚡ Removing all leftover vuex files and references * ✨ Removing final vuex refs * ⚡ Updating expected credentialId type * ⚡ Removing node credentials subscription code, reducing node click debounce timeout * 🐛 Fixing pushing nodes downstream when inserting new node * ✔️ Fixing a lint error in new type guard * ⚡ Updating helper reference * ✔️ Removing unnecessary awaits * ⚡ fix(editor): remove unnecessary imports from NDV * ⚡ Merging mapStores blocks in NodeView * ⚡ fix(editor): make sure JS Plumb not loaded earlier than needed * ⚡ Updating type guard nad credentials subscriptions * ⚡ Updating type guard so it doesn't use `any` type Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
This commit is contained in:
committed by
GitHub
parent
825637f02a
commit
bae3098e4e
305
packages/editor-ui/src/stores/credentials.ts
Normal file
305
packages/editor-ui/src/stores/credentials.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import { createNewCredential, deleteCredential, getAllCredentials, getCredentialData, getCredentialsNewName, getCredentialTypes, getForeignCredentials, oAuth1CredentialAuthorize, oAuth2CredentialAuthorize, testCredential, updateCredential } from "@/api/credentials";
|
||||
import { setCredentialSharedWith } from "@/api/credentials.ee";
|
||||
import { getAppNameFromCredType } from "@/components/helpers";
|
||||
import { EnterpriseEditionFeature, STORES } from "@/constants";
|
||||
import { ICredentialMap, ICredentialsDecryptedResponse, ICredentialsResponse, ICredentialsState, ICredentialTypeMap } from "@/Interface";
|
||||
import { i18n } from "@/plugins/i18n";
|
||||
import { ICredentialsDecrypted, ICredentialType, INodeCredentialTestResult, INodeProperties, INodeTypeDescription, IUser } from "n8n-workflow";
|
||||
import { defineStore } from "pinia";
|
||||
import Vue from "vue";
|
||||
import { useRootStore } from "./n8nRootStore";
|
||||
import { useNodeTypesStore } from "./nodeTypes";
|
||||
import { useSettingsStore } from "./settings";
|
||||
import { useUsersStore } from "./users";
|
||||
|
||||
const DEFAULT_CREDENTIAL_NAME = 'Unnamed credential';
|
||||
const DEFAULT_CREDENTIAL_POSTFIX = 'account';
|
||||
const TYPES_WITH_DEFAULT_NAME = ['httpBasicAuth', 'oAuth2Api', 'httpDigestAuth', 'oAuth1Api'];
|
||||
|
||||
export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
|
||||
state: (): ICredentialsState => ({
|
||||
credentialTypes: {},
|
||||
credentials: {},
|
||||
}),
|
||||
getters: {
|
||||
credentialTypesById(): Record<ICredentialType['name'], ICredentialType> {
|
||||
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));
|
||||
},
|
||||
allForeignCredentials(): ICredentialsResponse[] {
|
||||
return Object.values(this.foreignCredentials || {})
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
},
|
||||
allCredentialsByType(): {[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) => cred.type === type.name);
|
||||
|
||||
return accu;
|
||||
}, {});
|
||||
},
|
||||
getCredentialTypeByName() {
|
||||
return (type: string): ICredentialType => 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] || []);
|
||||
};
|
||||
},
|
||||
getNodesWithAccess() {
|
||||
return (credentialTypeName: string) => {
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const allLatestNodeTypes: INodeTypeDescription[] = nodeTypesStore.allLatestNodeTypes;
|
||||
|
||||
return allLatestNodeTypes.filter((nodeType: INodeTypeDescription) => {
|
||||
if (!nodeType.credentials) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const credentialTypeDescription of nodeType.credentials) {
|
||||
if (credentialTypeDescription.name === credentialTypeName ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
},
|
||||
getScopesByCredentialType() {
|
||||
return (credentialTypeName: string) => {
|
||||
const credentialType = this.getCredentialTypeByName(credentialTypeName) as {
|
||||
properties: INodeProperties[];
|
||||
};
|
||||
|
||||
const scopeProperty = credentialType.properties.find((p) => p.name === 'scope');
|
||||
|
||||
if (
|
||||
!scopeProperty ||
|
||||
!scopeProperty.default ||
|
||||
typeof scopeProperty.default !== 'string' ||
|
||||
scopeProperty.default === ''
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let { default: scopeDefault } = scopeProperty;
|
||||
|
||||
// disregard expressions for display
|
||||
scopeDefault = scopeDefault.replace(/^=/, '').replace(/\{\{.*\}\}/, '');
|
||||
|
||||
if (/ /.test(scopeDefault)) return scopeDefault.split(' ');
|
||||
|
||||
if (/,/.test(scopeDefault)) return scopeDefault.split(',');
|
||||
|
||||
return [scopeDefault];
|
||||
};
|
||||
},
|
||||
getCredentialOwnerName() {
|
||||
return (credentialId: string): string => {
|
||||
const credential = this.getCredentialById(credentialId);
|
||||
return credential && credential.ownedBy && credential.ownedBy.firstName
|
||||
? `${credential.ownedBy.firstName} ${credential.ownedBy.lastName} (${credential.ownedBy.email})`
|
||||
: i18n.baseText('credentialEdit.credentialSharing.info.sharee.fallback');
|
||||
};
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setCredentialTypes(credentialTypes: ICredentialType[]): void {
|
||||
this.credentialTypes = credentialTypes.reduce((accu: ICredentialTypeMap, cred: ICredentialType) => {
|
||||
accu[cred.name] = cred;
|
||||
|
||||
return accu;
|
||||
}, {});
|
||||
},
|
||||
setCredentials(credentials: ICredentialsResponse[]): void {
|
||||
this.credentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
|
||||
if (cred.id) {
|
||||
accu[cred.id] = cred;
|
||||
}
|
||||
return accu;
|
||||
}, {});
|
||||
},
|
||||
setForeignCredentials(credentials: ICredentialsResponse[]): void {
|
||||
this.foreignCredentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
|
||||
if (cred.id) {
|
||||
accu[cred.id] = cred;
|
||||
}
|
||||
return accu;
|
||||
}, {});
|
||||
},
|
||||
upsertCredential(credential: ICredentialsResponse): void {
|
||||
if (credential.id) {
|
||||
Vue.set(this.credentials, credential.id, { ...this.credentials[credential.id], ...credential });
|
||||
}
|
||||
},
|
||||
enableOAuthCredential(credential: ICredentialsResponse): void {
|
||||
// enable oauth event to track change between modals
|
||||
},
|
||||
async fetchCredentialTypes(forceFetch: boolean): Promise<void> {
|
||||
if (this.allCredentialTypes.length > 0 && forceFetch !== true) {
|
||||
return;
|
||||
}
|
||||
const rootStore = useRootStore();
|
||||
const credentialTypes = await getCredentialTypes(rootStore.getRestApiContext);
|
||||
this.setCredentialTypes(credentialTypes);
|
||||
},
|
||||
async fetchAllCredentials(): Promise<ICredentialsResponse[]> {
|
||||
const rootStore = useRootStore();
|
||||
const credentials = await getAllCredentials(rootStore.getRestApiContext);
|
||||
this.setCredentials(credentials);
|
||||
return credentials;
|
||||
},
|
||||
async fetchForeignCredentials(): Promise<ICredentialsResponse[]> {
|
||||
const rootStore = useRootStore();
|
||||
const credentials = await getForeignCredentials(rootStore.getRestApiContext);
|
||||
this.setForeignCredentials(credentials);
|
||||
return credentials;
|
||||
},
|
||||
async getCredentialData({ id }: {id: string}): Promise<ICredentialsResponse | ICredentialsDecryptedResponse | undefined> {
|
||||
const rootStore = useRootStore();
|
||||
return await getCredentialData(rootStore.getRestApiContext, id);
|
||||
},
|
||||
async createNewCredential(data: ICredentialsDecrypted): Promise<ICredentialsResponse> {
|
||||
const rootStore = useRootStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const credential = await createNewCredential(rootStore.getRestApiContext, data);
|
||||
|
||||
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
|
||||
this.upsertCredential(credential);
|
||||
|
||||
if (data.ownedBy) {
|
||||
this.setCredentialOwnedBy({
|
||||
credentialId: credential.id,
|
||||
ownedBy: data.ownedBy,
|
||||
});
|
||||
|
||||
const usersStore = useUsersStore();
|
||||
if (data.sharedWith && data.ownedBy.id === usersStore.currentUserId) {
|
||||
this.setCredentialSharedWith({
|
||||
credentialId: credential.id,
|
||||
sharedWith: data.sharedWith,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.upsertCredential(credential);
|
||||
}
|
||||
return credential;
|
||||
},
|
||||
async updateCredential(params: {data: ICredentialsDecrypted, id: string}): Promise<ICredentialsResponse> {
|
||||
const { id, data } = params;
|
||||
const rootStore = useRootStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const credential = await updateCredential(rootStore.getRestApiContext, id, data);
|
||||
|
||||
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
|
||||
this.upsertCredential(credential);
|
||||
|
||||
if (data.ownedBy) {
|
||||
this.setCredentialOwnedBy({
|
||||
credentialId: credential.id,
|
||||
ownedBy: data.ownedBy,
|
||||
});
|
||||
|
||||
const usersStore = useUsersStore();
|
||||
if (data.sharedWith && data.ownedBy.id === usersStore.currentUserId) {
|
||||
this.setCredentialSharedWith({
|
||||
credentialId: credential.id,
|
||||
sharedWith: data.sharedWith,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.upsertCredential(credential);
|
||||
}
|
||||
|
||||
return credential;
|
||||
},
|
||||
async deleteCredential({ id }: {id: string}): void {
|
||||
const rootStore = useRootStore();
|
||||
const deleted = await deleteCredential(rootStore.getRestApiContext, id);
|
||||
if (deleted) {
|
||||
Vue.delete(this.credentials, id);
|
||||
}
|
||||
},
|
||||
async oAuth2Authorize(data: ICredentialsResponse): Promise<string> {
|
||||
const rootStore = useRootStore();
|
||||
return oAuth2CredentialAuthorize(rootStore.getRestApiContext, data);
|
||||
},
|
||||
async oAuth1Authorize(data: ICredentialsResponse): Promise<string> {
|
||||
const rootStore = useRootStore();
|
||||
return oAuth1CredentialAuthorize(rootStore.getRestApiContext, data);
|
||||
},
|
||||
async testCredential(data: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
const rootStore = useRootStore();
|
||||
return testCredential(rootStore.getRestApiContext, { credentials: data });
|
||||
},
|
||||
async getNewCredentialName(params: { credentialTypeName: string }): Promise<string> {
|
||||
try {
|
||||
const { credentialTypeName } = params;
|
||||
let newName = DEFAULT_CREDENTIAL_NAME;
|
||||
if (!TYPES_WITH_DEFAULT_NAME.includes(credentialTypeName)) {
|
||||
const { displayName } = this.getCredentialTypeByName(credentialTypeName);
|
||||
newName = getAppNameFromCredType(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;
|
||||
}
|
||||
},
|
||||
|
||||
// Enterprise edition actions
|
||||
setCredentialOwnedBy(payload: { credentialId: string, ownedBy: Partial<IUser> }): void {
|
||||
Vue.set(this.credentials[payload.credentialId], 'ownedBy', payload.ownedBy);
|
||||
},
|
||||
async setCredentialSharedWith(payload: { sharedWith: IUser[]; credentialId: string; }): void {
|
||||
if(useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
|
||||
await setCredentialSharedWith(
|
||||
useRootStore().getRestApiContext,
|
||||
payload.credentialId,
|
||||
{
|
||||
shareWithIds: payload.sharedWith.map((sharee) => sharee.id),
|
||||
},
|
||||
);
|
||||
Vue.set(this.credentials[payload.credentialId], 'sharedWith', payload.sharedWith);
|
||||
}
|
||||
},
|
||||
addCredentialSharee(payload: { credentialId: string, sharee: Partial<IUser> }): void {
|
||||
Vue.set(
|
||||
this.credentials[payload.credentialId],
|
||||
'sharedWith',
|
||||
(this.credentials[payload.credentialId].sharedWith || []).concat([payload.sharee]),
|
||||
);
|
||||
},
|
||||
removeCredentialSharee(payload: { credentialId: string, sharee: Partial<IUser> }): void {
|
||||
Vue.set(
|
||||
this.credentials[payload.credentialId],
|
||||
'sharedWith',
|
||||
(this.credentials[payload.credentialId].sharedWith || [])
|
||||
.filter((sharee) => sharee.id !== payload.sharee.id),
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user