mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 21:07:28 -08:00
feat(editor): Add free AI credits CTA (#12365)
This commit is contained in:
parent
e26b406665
commit
f8731963f6
|
@ -364,6 +364,7 @@ export interface ICredentialsResponse extends ICredentialsEncrypted {
|
|||
currentUserHasAccess?: boolean;
|
||||
scopes?: Scope[];
|
||||
ownedBy?: Pick<IUserResponse, 'id' | 'firstName' | 'lastName' | 'email'>;
|
||||
isManaged: boolean;
|
||||
}
|
||||
|
||||
export interface ICredentialsBase {
|
||||
|
|
|
@ -18,4 +18,7 @@ export const credentialFactory = Factory.extend<ICredentialsResponse>({
|
|||
updatedAt() {
|
||||
return '';
|
||||
},
|
||||
isManaged() {
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ const createCredential = (overrides = {}): ICredentialsResponse => ({
|
|||
type: '',
|
||||
name: '',
|
||||
sharedWithProjects: [],
|
||||
isManaged: false,
|
||||
homeProject: {} as ProjectSharingData,
|
||||
...overrides,
|
||||
});
|
||||
|
|
|
@ -39,6 +39,7 @@ const props = withDefaults(
|
|||
name: '',
|
||||
sharedWithProjects: [],
|
||||
homeProject: {} as ProjectSharingData,
|
||||
isManaged: false,
|
||||
}),
|
||||
readOnly: false,
|
||||
},
|
||||
|
|
56
packages/editor-ui/src/components/CredentialConfig.test.ts
Normal file
56
packages/editor-ui/src/components/CredentialConfig.test.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import CredentialConfig from './CredentialEdit/CredentialConfig.vue';
|
||||
import { screen } from '@testing-library/vue';
|
||||
import type { ICredentialDataDecryptedObject, ICredentialType } from 'n8n-workflow';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import type { RenderOptions } from '@/__tests__/render';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import { STORES } from '@/constants';
|
||||
|
||||
const defaultRenderOptions: RenderOptions = {
|
||||
pinia: createTestingPinia({
|
||||
initialState: {
|
||||
[STORES.SETTINGS]: {
|
||||
settings: {
|
||||
enterprise: {
|
||||
sharing: false,
|
||||
externalSecrets: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
props: {
|
||||
isManaged: true,
|
||||
mode: 'edit',
|
||||
credentialType: {} as ICredentialType,
|
||||
credentialProperties: [],
|
||||
credentialData: {} as ICredentialDataDecryptedObject,
|
||||
credentialPermissions: {
|
||||
share: false,
|
||||
move: false,
|
||||
create: false,
|
||||
read: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
list: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const renderComponent = createComponentRenderer(CredentialConfig, defaultRenderOptions);
|
||||
|
||||
describe('CredentialConfig', () => {
|
||||
it('should display a warning when isManaged is true', async () => {
|
||||
renderComponent();
|
||||
expect(
|
||||
screen.queryByText('This is a managed credential and cannot be edited.'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display a warning when isManaged is false', async () => {
|
||||
renderComponent({ props: { isManaged: false } }, { merge: true });
|
||||
expect(
|
||||
screen.queryByText('This is a managed credential and cannot be edited.'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -55,6 +55,7 @@ type Props = {
|
|||
isRetesting?: boolean;
|
||||
requiredPropertiesFilled?: boolean;
|
||||
showAuthTypeSelector?: boolean;
|
||||
isManaged?: boolean;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
@ -235,7 +236,10 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<n8n-callout v-if="isManaged" theme="warning" icon="exclamation-triangle">
|
||||
{{ i18n.baseText('freeAi.credits.credentials.edit') }}
|
||||
</n8n-callout>
|
||||
<div v-else>
|
||||
<div :class="$style.config" data-test-id="node-credentials-config-container">
|
||||
<Banner
|
||||
v-show="showValidationWarning"
|
||||
|
|
|
@ -160,6 +160,11 @@ const credentialTypeName = computed(() => {
|
|||
return `${props.activeId}`;
|
||||
});
|
||||
|
||||
const isEditingManagedCredential = computed(() => {
|
||||
if (!props.activeId) return false;
|
||||
return credentialsStore.getCredentialById(props.activeId)?.isManaged ?? false;
|
||||
});
|
||||
|
||||
const isCredentialTestable = computed(() => {
|
||||
if (isOAuthType.value || !requiredPropertiesFilled.value) {
|
||||
return false;
|
||||
|
@ -597,6 +602,10 @@ function scrollToBottom() {
|
|||
}
|
||||
|
||||
async function retestCredential() {
|
||||
if (isEditingManagedCredential.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCredentialTestable.value || !credentialTypeName.value) {
|
||||
authError.value = '';
|
||||
testedSuccessfully.value = false;
|
||||
|
@ -1061,7 +1070,9 @@ function resetCredentialData(): void {
|
|||
<InlineNameEdit
|
||||
:model-value="credentialName"
|
||||
:subtitle="credentialType ? credentialType.displayName : ''"
|
||||
:readonly="!credentialPermissions.update || !credentialType"
|
||||
:readonly="
|
||||
!credentialPermissions.update || !credentialType || isEditingManagedCredential
|
||||
"
|
||||
type="Credential"
|
||||
data-test-id="credential-name"
|
||||
@update:model-value="onNameEdit"
|
||||
|
@ -1113,6 +1124,7 @@ function resetCredentialData(): void {
|
|||
:credential-properties="credentialProperties"
|
||||
:credential-data="credentialData"
|
||||
:credential-id="credentialId"
|
||||
:is-managed="isEditingManagedCredential"
|
||||
:show-validation-warning="showValidationWarning"
|
||||
:auth-error="authError"
|
||||
:tested-successfully="testedSuccessfully"
|
||||
|
|
185
packages/editor-ui/src/components/FreeAiCreditsCallout.test.ts
Normal file
185
packages/editor-ui/src/components/FreeAiCreditsCallout.test.ts
Normal file
|
@ -0,0 +1,185 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { fireEvent, screen } from '@testing-library/vue';
|
||||
import FreeAiCreditsCallout from '@/components/FreeAiCreditsCallout.vue';
|
||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { renderComponent } from '@/__tests__/render';
|
||||
import { mockedStore } from '@/__tests__/utils';
|
||||
|
||||
vi.mock('@/composables/useToast', () => ({
|
||||
useToast: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/settings.store', () => ({
|
||||
useSettingsStore: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/credentials.store', () => ({
|
||||
useCredentialsStore: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/users.store', () => ({
|
||||
useUsersStore: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/ndv.store', () => ({
|
||||
useNDVStore: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/posthog.store', () => ({
|
||||
usePostHog: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/projects.store', () => ({
|
||||
useProjectsStore: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/root.store', () => ({
|
||||
useRootStore: vi.fn(),
|
||||
}));
|
||||
|
||||
const assertUserCannotClaimCredits = () => {
|
||||
expect(screen.queryByText('Get 100 free OpenAI API credits')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Claim credits' })).not.toBeInTheDocument();
|
||||
};
|
||||
|
||||
const assertUserCanClaimCredits = () => {
|
||||
expect(screen.getByText('Get 100 free OpenAI API credits')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Claim credits' })).toBeInTheDocument();
|
||||
};
|
||||
|
||||
const assertUserClaimedCredits = () => {
|
||||
expect(screen.getByText('Claimed 100 free OpenAI API credits')).toBeInTheDocument();
|
||||
};
|
||||
|
||||
describe('FreeAiCreditsCallout', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
(useSettingsStore as any).mockReturnValue({
|
||||
isAiCreditsEnabled: true,
|
||||
aiCreditsQuota: 100,
|
||||
});
|
||||
|
||||
(useCredentialsStore as any).mockReturnValue({
|
||||
allCredentials: [],
|
||||
upsertCredential: vi.fn(),
|
||||
claimFreeAiCredits: vi.fn(),
|
||||
});
|
||||
|
||||
(useUsersStore as any).mockReturnValue({
|
||||
currentUser: {
|
||||
settings: {
|
||||
userClaimedAiCredits: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
(useNDVStore as any).mockReturnValue({
|
||||
activeNode: { type: '@n8n/n8n-nodes-langchain.openAi' },
|
||||
});
|
||||
|
||||
(usePostHog as any).mockReturnValue({
|
||||
isFeatureEnabled: vi.fn().mockReturnValue(true),
|
||||
});
|
||||
|
||||
(useProjectsStore as any).mockReturnValue({
|
||||
currentProject: { id: 'test-project-id' },
|
||||
});
|
||||
|
||||
(useRootStore as any).mockReturnValue({
|
||||
restApiContext: {},
|
||||
});
|
||||
|
||||
(useToast as any).mockReturnValue({
|
||||
showError: vi.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should shows the claim callout when the user can claim credits', () => {
|
||||
renderComponent(FreeAiCreditsCallout);
|
||||
|
||||
assertUserCanClaimCredits();
|
||||
});
|
||||
|
||||
it('should show success callout when credit are claimed', async () => {
|
||||
const credentialsStore = mockedStore(useCredentialsStore);
|
||||
|
||||
renderComponent(FreeAiCreditsCallout);
|
||||
|
||||
const claimButton = screen.getByRole('button', {
|
||||
name: 'Claim credits',
|
||||
});
|
||||
|
||||
await fireEvent.click(claimButton);
|
||||
|
||||
expect(credentialsStore.claimFreeAiCredits).toHaveBeenCalledWith('test-project-id');
|
||||
assertUserClaimedCredits();
|
||||
});
|
||||
|
||||
it('should not be able to claim credits is user already claimed credits', async () => {
|
||||
(useUsersStore as any).mockReturnValue({
|
||||
currentUser: {
|
||||
settings: {
|
||||
userClaimedAiCredits: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent(FreeAiCreditsCallout);
|
||||
|
||||
assertUserCannotClaimCredits();
|
||||
});
|
||||
|
||||
it('should not be able to claim credits is user does not have ai credits enabled', async () => {
|
||||
(useSettingsStore as any).mockReturnValue({
|
||||
isAiCreditsEnabled: false,
|
||||
aiCreditsQuota: 0,
|
||||
});
|
||||
|
||||
renderComponent(FreeAiCreditsCallout);
|
||||
|
||||
assertUserCannotClaimCredits();
|
||||
});
|
||||
|
||||
it('should not be able to claim credits if user it is not in experiment', async () => {
|
||||
(usePostHog as any).mockReturnValue({
|
||||
isFeatureEnabled: vi.fn().mockReturnValue(false),
|
||||
});
|
||||
|
||||
renderComponent(FreeAiCreditsCallout);
|
||||
|
||||
assertUserCannotClaimCredits();
|
||||
});
|
||||
|
||||
it('should not be able to claim credits if user already has OpenAiApi credential', async () => {
|
||||
(useCredentialsStore as any).mockReturnValue({
|
||||
allCredentials: [
|
||||
{
|
||||
type: 'openAiApi',
|
||||
},
|
||||
],
|
||||
upsertCredential: vi.fn(),
|
||||
});
|
||||
|
||||
renderComponent(FreeAiCreditsCallout);
|
||||
|
||||
assertUserCannotClaimCredits();
|
||||
});
|
||||
|
||||
it('should not be able to claim credits if active node it is not a valid node', async () => {
|
||||
(useNDVStore as any).mockReturnValue({
|
||||
activeNode: { type: '@n8n/n8n-nodes.jira' },
|
||||
});
|
||||
|
||||
renderComponent(FreeAiCreditsCallout);
|
||||
|
||||
assertUserCannotClaimCredits();
|
||||
});
|
||||
});
|
118
packages/editor-ui/src/components/FreeAiCreditsCallout.vue
Normal file
118
packages/editor-ui/src/components/FreeAiCreditsCallout.vue
Normal file
|
@ -0,0 +1,118 @@
|
|||
<script lang="ts" setup>
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { AI_CREDITS_EXPERIMENT } from '@/constants';
|
||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const OPEN_AI_API_CREDENTIAL_TYPE = 'openAiApi';
|
||||
|
||||
const LANGCHAIN_NODES_PREFIX = '@n8n/n8n-nodes-langchain.';
|
||||
|
||||
const N8N_NODES_PREFIX = '@n8n/n8n-nodes.';
|
||||
|
||||
const NODES_WITH_OPEN_AI_API_CREDENTIAL = [
|
||||
`${LANGCHAIN_NODES_PREFIX}openAi`,
|
||||
`${LANGCHAIN_NODES_PREFIX}embeddingsOpenAi`,
|
||||
`${LANGCHAIN_NODES_PREFIX}lmChatOpenAi`,
|
||||
`${N8N_NODES_PREFIX}openAi`,
|
||||
];
|
||||
|
||||
const showSuccessCallout = ref(false);
|
||||
const claimingCredits = ref(false);
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const postHogStore = usePostHog();
|
||||
const credentialsStore = useCredentialsStore();
|
||||
const usersStore = useUsersStore();
|
||||
const ndvStore = useNDVStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const i18n = useI18n();
|
||||
const toast = useToast();
|
||||
|
||||
const userHasOpenAiCredentialAlready = computed(
|
||||
() =>
|
||||
!!credentialsStore.allCredentials.filter(
|
||||
(credential) => credential.type === OPEN_AI_API_CREDENTIAL_TYPE,
|
||||
).length,
|
||||
);
|
||||
|
||||
const userHasClaimedAiCreditsAlready = computed(
|
||||
() => !!usersStore.currentUser?.settings?.userClaimedAiCredits,
|
||||
);
|
||||
|
||||
const activeNodeHasOpenAiApiCredential = computed(
|
||||
() =>
|
||||
ndvStore.activeNode?.type &&
|
||||
NODES_WITH_OPEN_AI_API_CREDENTIAL.includes(ndvStore.activeNode.type),
|
||||
);
|
||||
|
||||
const userCanClaimOpenAiCredits = computed(() => {
|
||||
return (
|
||||
settingsStore.isAiCreditsEnabled &&
|
||||
activeNodeHasOpenAiApiCredential.value &&
|
||||
postHogStore.isFeatureEnabled(AI_CREDITS_EXPERIMENT.name) &&
|
||||
!userHasOpenAiCredentialAlready.value &&
|
||||
!userHasClaimedAiCreditsAlready.value
|
||||
);
|
||||
});
|
||||
|
||||
const onClaimCreditsClicked = async () => {
|
||||
claimingCredits.value = true;
|
||||
|
||||
try {
|
||||
await credentialsStore.claimFreeAiCredits(projectsStore.currentProject?.id);
|
||||
|
||||
if (usersStore?.currentUser?.settings) {
|
||||
usersStore.currentUser.settings.userClaimedAiCredits = true;
|
||||
}
|
||||
|
||||
showSuccessCallout.value = true;
|
||||
} catch (e) {
|
||||
toast.showError(
|
||||
e,
|
||||
i18n.baseText('freeAi.credits.showError.claim.title'),
|
||||
i18n.baseText('freeAi.credits.showError.claim.message'),
|
||||
);
|
||||
} finally {
|
||||
claimingCredits.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="mt-xs">
|
||||
<n8n-callout
|
||||
v-if="userCanClaimOpenAiCredits && !showSuccessCallout"
|
||||
theme="secondary"
|
||||
icon="exclamation-circle"
|
||||
>
|
||||
{{
|
||||
i18n.baseText('freeAi.credits.callout.claim.title', {
|
||||
interpolate: { credits: settingsStore.aiCreditsQuota },
|
||||
})
|
||||
}}
|
||||
<template #trailingContent>
|
||||
<n8n-button
|
||||
type="tertiary"
|
||||
size="small"
|
||||
:label="i18n.baseText('freeAi.credits.callout.claim.button.label')"
|
||||
:loading="claimingCredits"
|
||||
@click="onClaimCreditsClicked"
|
||||
/>
|
||||
</template>
|
||||
</n8n-callout>
|
||||
<n8n-callout v-else-if="showSuccessCallout" theme="success" icon="check-circle">
|
||||
{{
|
||||
i18n.baseText('freeAi.credits.callout.success.title', {
|
||||
interpolate: { credits: settingsStore.aiCreditsQuota },
|
||||
})
|
||||
}}
|
||||
</n8n-callout>
|
||||
</div>
|
||||
</template>
|
|
@ -48,6 +48,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
|||
import { importCurlEventBus, ndvEventBus } from '@/event-bus';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
import { updateDynamicConnections } from '@/utils/nodeSettingsUtils';
|
||||
import FreeAiCreditsCallout from '@/components/FreeAiCreditsCallout.vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -1026,6 +1027,7 @@ onBeforeUnmount(() => {
|
|||
})
|
||||
"
|
||||
/>
|
||||
<FreeAiCreditsCallout />
|
||||
<div v-show="openPanel === 'params'">
|
||||
<NodeWebhooks :node="node" :node-type-description="nodeType" />
|
||||
|
||||
|
|
|
@ -2805,5 +2805,11 @@
|
|||
"testDefinition.notImplemented": "This feature is not implemented yet!",
|
||||
"testDefinition.viewDetails": "View Details",
|
||||
"testDefinition.editTest": "Edit Test",
|
||||
"testDefinition.deleteTest": "Delete Test"
|
||||
"testDefinition.deleteTest": "Delete Test",
|
||||
"freeAi.credits.callout.claim.title": "Get {credits} free OpenAI API credits",
|
||||
"freeAi.credits.callout.claim.button.label": "Claim credits",
|
||||
"freeAi.credits.callout.success.title": "Claimed {credits} free OpenAI API credits",
|
||||
"freeAi.credits.credentials.edit": "This is a managed credential and cannot be edited.",
|
||||
"freeAi.credits.showError.claim.title": "Free AI credits",
|
||||
"freeAi.credits.showError.claim.message": "Enable to claim credits"
|
||||
}
|
||||
|
|
|
@ -163,6 +163,7 @@ import {
|
|||
faStream,
|
||||
faPowerOff,
|
||||
faPaperPlane,
|
||||
faExclamationCircle,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { faVariable, faXmark, faVault, faRefresh } from './custom';
|
||||
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
|
||||
|
@ -228,6 +229,7 @@ export const FontAwesomePlugin: Plugin = {
|
|||
addIcon(faEquals);
|
||||
addIcon(faEye);
|
||||
addIcon(faExclamationTriangle);
|
||||
addIcon(faExclamationCircle);
|
||||
addIcon(faExpand);
|
||||
addIcon(faExpandAlt);
|
||||
addIcon(faExternalLinkAlt);
|
||||
|
|
|
@ -26,6 +26,7 @@ import { computed, ref } from 'vue';
|
|||
import { useNodeTypesStore } from './nodeTypes.store';
|
||||
import { useRootStore } from './root.store';
|
||||
import { useSettingsStore } from './settings.store';
|
||||
import * as aiApi from '@/api/ai';
|
||||
|
||||
const DEFAULT_CREDENTIAL_NAME = 'Unnamed credential';
|
||||
const DEFAULT_CREDENTIAL_POSTFIX = 'account';
|
||||
|
@ -36,6 +37,8 @@ export type CredentialsStore = ReturnType<typeof useCredentialsStore>;
|
|||
export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
||||
const state = ref<ICredentialsState>({ credentialTypes: {}, credentials: {} });
|
||||
|
||||
const rootStore = useRootStore();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #region Computed
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -252,7 +255,6 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
if (allCredentialTypes.value.length > 0 && !forceFetch) {
|
||||
return;
|
||||
}
|
||||
const rootStore = useRootStore();
|
||||
const credentialTypes = await credentialsApi.getCredentialTypes(rootStore.baseUrl);
|
||||
setCredentialTypes(credentialTypes);
|
||||
};
|
||||
|
@ -261,8 +263,6 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
projectId?: string,
|
||||
includeScopes = true,
|
||||
): Promise<ICredentialsResponse[]> => {
|
||||
const rootStore = useRootStore();
|
||||
|
||||
const filter = {
|
||||
projectId,
|
||||
};
|
||||
|
@ -279,8 +279,6 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
const fetchAllCredentialsForWorkflow = async (
|
||||
options: { workflowId: string } | { projectId: string },
|
||||
): Promise<ICredentialsResponse[]> => {
|
||||
const rootStore = useRootStore();
|
||||
|
||||
const credentials = await credentialsApi.getAllCredentialsForWorkflow(
|
||||
rootStore.restApiContext,
|
||||
options,
|
||||
|
@ -294,7 +292,6 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
}: {
|
||||
id: string;
|
||||
}): Promise<ICredentialsResponse | ICredentialsDecryptedResponse | undefined> => {
|
||||
const rootStore = useRootStore();
|
||||
return await credentialsApi.getCredentialData(rootStore.restApiContext, id);
|
||||
};
|
||||
|
||||
|
@ -302,7 +299,6 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
data: ICredentialsDecrypted,
|
||||
projectId?: string,
|
||||
): Promise<ICredentialsResponse> => {
|
||||
const rootStore = useRootStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const credential = await credentialsApi.createNewCredential(
|
||||
rootStore.restApiContext,
|
||||
|
@ -333,7 +329,6 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
id: string;
|
||||
}): Promise<ICredentialsResponse> => {
|
||||
const { id, data } = params;
|
||||
const rootStore = useRootStore();
|
||||
const credential = await credentialsApi.updateCredential(rootStore.restApiContext, id, data);
|
||||
|
||||
upsertCredential(credential);
|
||||
|
@ -342,7 +337,6 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
};
|
||||
|
||||
const deleteCredential = async ({ id }: { id: string }) => {
|
||||
const rootStore = useRootStore();
|
||||
const deleted = await credentialsApi.deleteCredential(rootStore.restApiContext, id);
|
||||
if (deleted) {
|
||||
const { [id]: deletedCredential, ...rest } = state.value.credentials;
|
||||
|
@ -351,19 +345,16 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
};
|
||||
|
||||
const oAuth2Authorize = async (data: ICredentialsResponse): Promise<string> => {
|
||||
const rootStore = useRootStore();
|
||||
return await credentialsApi.oAuth2CredentialAuthorize(rootStore.restApiContext, data);
|
||||
};
|
||||
|
||||
const oAuth1Authorize = async (data: ICredentialsResponse): Promise<string> => {
|
||||
const rootStore = useRootStore();
|
||||
return await credentialsApi.oAuth1CredentialAuthorize(rootStore.restApiContext, data);
|
||||
};
|
||||
|
||||
const testCredential = async (
|
||||
data: ICredentialsDecrypted,
|
||||
): Promise<INodeCredentialTestResult> => {
|
||||
const rootStore = useRootStore();
|
||||
return await credentialsApi.testCredential(rootStore.restApiContext, { credentials: data });
|
||||
};
|
||||
|
||||
|
@ -377,7 +368,6 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
newName =
|
||||
newName.length > 0 ? `${newName} ${DEFAULT_CREDENTIAL_POSTFIX}` : DEFAULT_CREDENTIAL_NAME;
|
||||
}
|
||||
const rootStore = useRootStore();
|
||||
const res = await credentialsApi.getCredentialsNewName(rootStore.restApiContext, newName);
|
||||
return res.name;
|
||||
} catch (e) {
|
||||
|
@ -407,12 +397,19 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
};
|
||||
|
||||
const getCredentialTranslation = async (credentialType: string): Promise<object> => {
|
||||
const rootStore = useRootStore();
|
||||
return await makeRestApiRequest(rootStore.restApiContext, 'GET', '/credential-translation', {
|
||||
credentialType,
|
||||
});
|
||||
};
|
||||
|
||||
const claimFreeAiCredits = async (projectId?: string): Promise<ICredentialsResponse> => {
|
||||
const credential = await aiApi.claimFreeAiCredits(rootStore.restApiContext, {
|
||||
projectId,
|
||||
});
|
||||
upsertCredential(credential);
|
||||
return credential;
|
||||
};
|
||||
|
||||
// #endregion
|
||||
|
||||
return {
|
||||
|
@ -449,6 +446,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
|||
testCredential,
|
||||
getCredentialTranslation,
|
||||
setCredentialSharedWith,
|
||||
claimFreeAiCredits,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ describe('CredentialsView', () => {
|
|||
type: 'test',
|
||||
createdAt: '2021-05-05T00:00:00Z',
|
||||
updatedAt: '2021-05-05T00:00:00Z',
|
||||
isManaged: false,
|
||||
},
|
||||
];
|
||||
const projectsStore = mockedStore(useProjectsStore);
|
||||
|
@ -77,6 +78,7 @@ describe('CredentialsView', () => {
|
|||
createdAt: '2021-05-05T00:00:00Z',
|
||||
updatedAt: '2021-05-05T00:00:00Z',
|
||||
scopes: ['credential:update'],
|
||||
isManaged: false,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
|
@ -84,6 +86,7 @@ describe('CredentialsView', () => {
|
|||
type: 'test2',
|
||||
createdAt: '2021-05-05T00:00:00Z',
|
||||
updatedAt: '2021-05-05T00:00:00Z',
|
||||
isManaged: false,
|
||||
},
|
||||
];
|
||||
const projectsStore = mockedStore(useProjectsStore);
|
||||
|
@ -124,6 +127,7 @@ describe('CredentialsView', () => {
|
|||
createdAt: '2021-05-05T00:00:00Z',
|
||||
updatedAt: '2021-05-05T00:00:00Z',
|
||||
scopes: ['credential:update'],
|
||||
isManaged: false,
|
||||
},
|
||||
];
|
||||
const { getByTestId } = renderComponent();
|
||||
|
|
|
@ -51,5 +51,6 @@ export const newCredential = (
|
|||
updatedAt: faker.date.past().toISOString(),
|
||||
id: faker.string.alphanumeric({ length: 16 }),
|
||||
name: faker.commerce.productName(),
|
||||
isManaged: false,
|
||||
...opts,
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue