refactor(editor): Extract API Requests into @n8n/rest-api-client package (no-changelog) (#15930)

This commit is contained in:
Alex Grozav
2025-06-05 12:08:10 +02:00
committed by GitHub
parent a18822af0e
commit 6cf07200dc
90 changed files with 502 additions and 279 deletions

View File

@@ -1177,11 +1177,6 @@ export interface CommunityNodesState {
installedPackages: CommunityPackageMap;
}
export interface IRestApiContext {
baseUrl: string;
pushRef: string;
}
export interface IZoomConfig {
scale: number;
offset: XYPosition;

View File

@@ -1,8 +1,9 @@
import { useAIAssistantHelpers } from '@/composables/useAIAssistantHelpers';
import { AI_ASSISTANT_MAX_CONTENT_LENGTH } from '@/constants';
import type { ICredentialsResponse, IRestApiContext } from '@/Interface';
import type { ICredentialsResponse } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import type { AskAiRequest, ChatRequest, ReplaceCodeRequest } from '@/types/assistant.types';
import { makeRestApiRequest, streamRequest } from '@/utils/apiUtils';
import { makeRestApiRequest, streamRequest } from '@n8n/rest-api-client';
import { getObjectSizeInKB } from '@/utils/objectUtils';
import type { IDataObject } from 'n8n-workflow';

View File

@@ -1,39 +0,0 @@
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type {
CreateApiKeyRequestDto,
UpdateApiKeyRequestDto,
ApiKey,
ApiKeyWithRawValue,
} from '@n8n/api-types';
import type { ApiKeyScope } from '@n8n/permissions';
export async function getApiKeys(context: IRestApiContext): Promise<ApiKey[]> {
return await makeRestApiRequest(context, 'GET', '/api-keys');
}
export async function getApiKeyScopes(context: IRestApiContext): Promise<ApiKeyScope[]> {
return await makeRestApiRequest(context, 'GET', '/api-keys/scopes');
}
export async function createApiKey(
context: IRestApiContext,
payload: CreateApiKeyRequestDto,
): Promise<ApiKeyWithRawValue> {
return await makeRestApiRequest(context, 'POST', '/api-keys', payload);
}
export async function deleteApiKey(
context: IRestApiContext,
id: string,
): Promise<{ success: boolean }> {
return await makeRestApiRequest(context, 'DELETE', `/api-keys/${id}`);
}
export async function updateApiKey(
context: IRestApiContext,
id: string,
payload: UpdateApiKeyRequestDto,
): Promise<{ success: boolean }> {
return await makeRestApiRequest(context, 'PATCH', `/api-keys/${id}`, payload);
}

View File

@@ -1,5 +1,6 @@
import type { Cloud, IRestApiContext, InstanceUsage } from '@/Interface';
import { get, post } from '@/utils/apiUtils';
import type { Cloud, InstanceUsage } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { get, post } from '@n8n/rest-api-client';
export async function getCurrentPlan(context: IRestApiContext): Promise<Cloud.PlanData> {
return await get(context.baseUrl, '/admin/cloud-plan');

View File

@@ -1,30 +0,0 @@
import type { IRestApiContext } from '@/Interface';
import type { PublicInstalledPackage } from 'n8n-workflow';
import { get, post, makeRestApiRequest } from '@/utils/apiUtils';
export async function getInstalledCommunityNodes(
context: IRestApiContext,
): Promise<PublicInstalledPackage[]> {
const response = await get(context.baseUrl, '/community-packages');
return response.data || [];
}
export async function installNewPackage(
context: IRestApiContext,
name: string,
verify?: boolean,
version?: string,
): Promise<PublicInstalledPackage> {
return await post(context.baseUrl, '/community-packages', { name, verify, version });
}
export async function uninstallPackage(context: IRestApiContext, name: string): Promise<void> {
return await makeRestApiRequest(context, 'DELETE', '/community-packages', { name });
}
export async function updatePackage(
context: IRestApiContext,
name: string,
): Promise<PublicInstalledPackage> {
return await makeRestApiRequest(context, 'PATCH', '/community-packages', { name });
}

View File

@@ -1,5 +1,6 @@
import type { ICredentialsResponse, IRestApiContext, IShareCredentialsPayload } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { ICredentialsResponse, IShareCredentialsPayload } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type { IDataObject } from 'n8n-workflow';
export async function setCredentialSharedWith(

View File

@@ -1,9 +1,6 @@
import type {
ICredentialsDecryptedResponse,
ICredentialsResponse,
IRestApiContext,
} from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { ICredentialsDecryptedResponse, ICredentialsResponse } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type {
ICredentialsDecrypted,
ICredentialType,

View File

@@ -1,8 +0,0 @@
import type { IRestApiContext } from '@/Interface';
import { get } from '@/utils/apiUtils';
export async function getBecomeCreatorCta(context: IRestApiContext): Promise<boolean> {
const response = await get(context.baseUrl, '/cta/become-creator');
return response;
}

View File

@@ -1,5 +1,6 @@
import type { EnvironmentVariable, IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { EnvironmentVariable } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type { IDataObject } from 'n8n-workflow';
export async function getVariables(context: IRestApiContext): Promise<EnvironmentVariable[]> {

View File

@@ -1,5 +1,5 @@
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest, request } from '@/utils/apiUtils';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { makeRestApiRequest, request } from '@n8n/rest-api-client';
export interface TestRunRecord {
id: string;

View File

@@ -1,49 +0,0 @@
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { IDataObject, MessageEventBusDestinationOptions } from 'n8n-workflow';
export type ApiMessageEventBusDestinationOptions = MessageEventBusDestinationOptions & {
id: string;
};
export function hasDestinationId(
destination: MessageEventBusDestinationOptions,
): destination is ApiMessageEventBusDestinationOptions {
return destination.id !== undefined;
}
export async function saveDestinationToDb(
context: IRestApiContext,
destination: ApiMessageEventBusDestinationOptions,
subscribedEvents: string[] = [],
) {
const data: IDataObject = {
...destination,
subscribedEvents,
};
return await makeRestApiRequest(context, 'POST', '/eventbus/destination', data);
}
export async function deleteDestinationFromDb(context: IRestApiContext, destinationId: string) {
return await makeRestApiRequest(context, 'DELETE', `/eventbus/destination?id=${destinationId}`);
}
export async function sendTestMessageToDestination(
context: IRestApiContext,
destination: ApiMessageEventBusDestinationOptions,
): Promise<boolean> {
const data: IDataObject = {
...destination,
};
return await makeRestApiRequest(context, 'GET', '/eventbus/testmessage', data);
}
export async function getEventNamesFromBackend(context: IRestApiContext): Promise<string[]> {
return await makeRestApiRequest(context, 'GET', '/eventbus/eventnames');
}
export async function getDestinationsFromBackend(
context: IRestApiContext,
): Promise<MessageEventBusDestinationOptions[]> {
return await makeRestApiRequest(context, 'GET', '/eventbus/destination');
}

View File

@@ -1,6 +0,0 @@
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
export async function sessionStarted(context: IRestApiContext): Promise<void> {
return await makeRestApiRequest(context, 'GET', '/events/session-started');
}

View File

@@ -1,5 +1,6 @@
import type { IRestApiContext, ExternalSecretsProvider } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { ExternalSecretsProvider } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { makeRestApiRequest } from '@n8n/rest-api-client';
export const getExternalSecrets = async (
context: IRestApiContext,

View File

@@ -1,11 +1,7 @@
import type {
CurrentUserResponse,
IInviteResponse,
IRestApiContext,
InvitableRoleName,
} from '@/Interface';
import type { CurrentUserResponse, IInviteResponse, InvitableRoleName } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import type { IDataObject } from 'n8n-workflow';
import { makeRestApiRequest } from '@/utils/apiUtils';
import { makeRestApiRequest } from '@n8n/rest-api-client';
type AcceptInvitationParams = {
inviterId: string;

View File

@@ -1,5 +1,6 @@
import type { ILdapConfig, ILdapSyncData, IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { ILdapConfig, ILdapSyncData } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type { IDataObject } from 'n8n-workflow';
export async function getLdapConfig(context: IRestApiContext): Promise<ILdapConfig> {

View File

@@ -1,35 +0,0 @@
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
export async function canEnableMFA(context: IRestApiContext) {
return await makeRestApiRequest(context, 'POST', '/mfa/can-enable');
}
export async function getMfaQR(
context: IRestApiContext,
): Promise<{ qrCode: string; secret: string; recoveryCodes: string[] }> {
return await makeRestApiRequest(context, 'GET', '/mfa/qr');
}
export async function enableMfa(
context: IRestApiContext,
data: { mfaCode: string },
): Promise<void> {
return await makeRestApiRequest(context, 'POST', '/mfa/enable', data);
}
export async function verifyMfaCode(
context: IRestApiContext,
data: { mfaCode: string },
): Promise<void> {
return await makeRestApiRequest(context, 'POST', '/mfa/verify', data);
}
export type DisableMfaParams = {
mfaCode?: string;
mfaRecoveryCode?: string;
};
export async function disableMfa(context: IRestApiContext, data: DisableMfaParams): Promise<void> {
return await makeRestApiRequest(context, 'POST', '/mfa/disable', data);
}

View File

@@ -1,123 +0,0 @@
import type {
ActionResultRequestDto,
CommunityNodeType,
OptionsRequestDto,
ResourceLocatorRequestDto,
ResourceMapperFieldsRequestDto,
} from '@n8n/api-types';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { IRestApiContext } from '@/Interface';
import type { INodeTranslationHeaders } from '@n8n/i18n';
import axios from 'axios';
import {
type INodeListSearchResult,
type INodePropertyOptions,
type INodeTypeDescription,
type INodeTypeNameVersion,
type NodeParameterValueType,
type ResourceMapperFields,
sleep,
} from 'n8n-workflow';
async function fetchNodeTypesJsonWithRetry(url: string, retries = 5, delay = 500) {
for (let attempt = 0; attempt < retries; attempt++) {
const response = await axios.get(url, { withCredentials: true });
if (typeof response.data === 'object' && response.data !== null) {
return response.data;
}
await sleep(delay * attempt);
}
throw new Error('Could not fetch node types');
}
export async function getNodeTypes(baseUrl: string) {
return await fetchNodeTypesJsonWithRetry(baseUrl + 'types/nodes.json');
}
export async function fetchCommunityNodeTypes(
context: IRestApiContext,
): Promise<CommunityNodeType[]> {
return await makeRestApiRequest(context, 'GET', '/community-node-types');
}
export async function fetchCommunityNodeAttributes(
context: IRestApiContext,
type: string,
): Promise<CommunityNodeType | null> {
return await makeRestApiRequest(
context,
'GET',
`/community-node-types/${encodeURIComponent(type)}`,
);
}
export async function getNodeTranslationHeaders(
context: IRestApiContext,
): Promise<INodeTranslationHeaders | undefined> {
return await makeRestApiRequest(context, 'GET', '/node-translation-headers');
}
export async function getNodesInformation(
context: IRestApiContext,
nodeInfos: INodeTypeNameVersion[],
): Promise<INodeTypeDescription[]> {
return await makeRestApiRequest(context, 'POST', '/node-types', { nodeInfos });
}
export async function getNodeParameterOptions(
context: IRestApiContext,
sendData: OptionsRequestDto,
): Promise<INodePropertyOptions[]> {
return await makeRestApiRequest(context, 'POST', '/dynamic-node-parameters/options', sendData);
}
export async function getResourceLocatorResults(
context: IRestApiContext,
sendData: ResourceLocatorRequestDto,
): Promise<INodeListSearchResult> {
return await makeRestApiRequest(
context,
'POST',
'/dynamic-node-parameters/resource-locator-results',
sendData,
);
}
export async function getResourceMapperFields(
context: IRestApiContext,
sendData: ResourceMapperFieldsRequestDto,
): Promise<ResourceMapperFields> {
return await makeRestApiRequest(
context,
'POST',
'/dynamic-node-parameters/resource-mapper-fields',
sendData,
);
}
export async function getLocalResourceMapperFields(
context: IRestApiContext,
sendData: ResourceMapperFieldsRequestDto,
): Promise<ResourceMapperFields> {
return await makeRestApiRequest(
context,
'POST',
'/dynamic-node-parameters/local-resource-mapper-fields',
sendData,
);
}
export async function getNodeParameterActionResult(
context: IRestApiContext,
sendData: ActionResultRequestDto,
): Promise<NodeParameterValueType> {
return await makeRestApiRequest(
context,
'POST',
'/dynamic-node-parameters/action-result',
sendData,
);
}

View File

@@ -1,7 +0,0 @@
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { NpsSurveyState } from 'n8n-workflow';
export async function updateNpsSurveyState(context: IRestApiContext, state: NpsSurveyState) {
await makeRestApiRequest(context, 'PATCH', '/user-settings/nps-survey', state);
}

View File

@@ -1,8 +0,0 @@
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
const GET_STATUS_ENDPOINT = '/orchestration/worker/status';
export const sendGetWorkerStatus = async (context: IRestApiContext): Promise<void> => {
await makeRestApiRequest(context, 'POST', GET_STATUS_ENDPOINT);
};

View File

@@ -1,5 +1,5 @@
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type { Project, ProjectListItem, ProjectsCount } from '@/types/projects.types';
import type { CreateProjectDto, UpdateProjectDto } from '@n8n/api-types';

View File

@@ -1,7 +0,0 @@
import type { AllRolesMap } from '@n8n/permissions';
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
export const getRoles = async (context: IRestApiContext): Promise<AllRolesMap> => {
return await makeRestApiRequest(context, 'GET', '/roles');
};

View File

@@ -1,7 +1,7 @@
import { getSchemaPreview } from './schemaPreview';
import * as apiUtils from '@/utils/apiUtils';
import * as apiUtils from '@n8n/rest-api-client';
vi.mock('@/utils/apiUtils');
vi.mock('@n8n/rest-api-client');
describe('API: schemaPreview', () => {
describe('getSchemaPreview', () => {

View File

@@ -1,4 +1,4 @@
import { request } from '@/utils/apiUtils';
import { request } from '@n8n/rest-api-client';
import type { JSONSchema7 } from 'json-schema';
import type { NodeParameterValueType } from 'n8n-workflow';
import { isEmpty } from '@/utils/typesUtils';

View File

@@ -1,5 +1,6 @@
import type { IRestApiContext, IN8nPrompts, IN8nPromptResponse } from '../Interface';
import { makeRestApiRequest, get, post } from '@/utils/apiUtils';
import type { IN8nPrompts, IN8nPromptResponse } from '../Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { makeRestApiRequest, get, post } from '@n8n/rest-api-client';
import { N8N_IO_BASE_URL, NPM_COMMUNITY_NODE_SEARCH_API_URL } from '@/constants';
import type { FrontendSettings } from '@n8n/api-types';

View File

@@ -3,14 +3,14 @@ import type {
PushWorkFolderRequestDto,
SourceControlledFile,
} from '@n8n/api-types';
import type { IRestApiContext } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import type {
SourceControlPreferences,
SourceControlStatus,
SshKeyTypes,
} from '@/types/sourceControl.types';
import { makeRestApiRequest } from '@/utils/apiUtils';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type { TupleToUnion } from '@/utils/typeHelpers';
const sourceControlApiRoot = '/source-control';

View File

@@ -1,6 +1,7 @@
import type { SamlPreferences, SamlToggleDto } from '@n8n/api-types';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { IRestApiContext, SamlPreferencesExtractedData } from '@/Interface';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type { SamlPreferencesExtractedData } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
export const initSSO = async (context: IRestApiContext, redirectUrl = ''): Promise<string> => {
return await makeRestApiRequest(context, 'GET', `/sso/saml/initsso?redirect=${redirectUrl}`);

View File

@@ -1,5 +1,6 @@
import type { IRestApiContext, ITag } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { ITag } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type { CreateOrUpdateTagRequestDto, RetrieveTagQueryDto } from '@n8n/api-types';
type TagsApiEndpoint = '/tags' | '/annotation-tags';

View File

@@ -9,7 +9,7 @@ import type {
IWorkflowTemplate,
TemplateSearchFacet,
} from '@/Interface';
import { get } from '@/utils/apiUtils';
import { get } from '@n8n/rest-api-client';
function stringifyArray(arr: string[]) {
return arr.join(',');

View File

@@ -1,12 +0,0 @@
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { BannerName } from '@n8n/api-types';
export async function dismissBannerPermanently(
context: IRestApiContext,
data: { bannerName: BannerName; dismissedBanners: string[] },
): Promise<void> {
return await makeRestApiRequest(context, 'POST', '/owner/dismiss-banner', {
banner: data.bannerName,
});
}

View File

@@ -1,6 +1,7 @@
import type { CommunityRegisteredRequestDto } from '@n8n/api-types';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { IRestApiContext, UsageState } from '@/Interface';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type { UsageState } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
export const getLicense = async (context: IRestApiContext): Promise<UsageState['data']> => {
return await makeRestApiRequest(context, 'GET', '/license');

View File

@@ -7,12 +7,12 @@ import type {
import type {
CurrentUserResponse,
IPersonalizationLatestVersion,
IRestApiContext,
IUserResponse,
InvitableRoleName,
} from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import type { IDataObject, IUserSettings } from 'n8n-workflow';
import { makeRestApiRequest } from '@/utils/apiUtils';
import { makeRestApiRequest } from '@n8n/rest-api-client';
export async function loginCurrentUser(
context: IRestApiContext,

View File

@@ -1,6 +1,6 @@
import type { IVersion } from '@/Interface';
import { INSTANCE_ID_HEADER } from '@/constants';
import { get } from '@/utils/apiUtils';
import { get } from '@n8n/rest-api-client';
export async function getNextVersions(
endpoint: string,

View File

@@ -1,17 +0,0 @@
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { IRestApiContext } from '@/Interface';
import type { IHttpRequestMethods } from 'n8n-workflow';
type WebhookData = {
workflowId: string;
webhookPath: string;
method: IHttpRequestMethods;
node: string;
};
export const findWebhook = async (
context: IRestApiContext,
data: { path: string; method: string },
): Promise<WebhookData | null> => {
return await makeRestApiRequest(context, 'POST', '/webhooks/find', data);
};

View File

@@ -1,5 +1,5 @@
import type { IUser } from '@/Interface';
import { post } from '@/utils/apiUtils';
import { post } from '@n8n/rest-api-client';
const N8N_API_BASE_URL = 'https://api.n8n.io/api';
const CONTACT_EMAIL_SUBMISSION_ENDPOINT = '/accounts/onboarding';

View File

@@ -1,5 +1,5 @@
import type { IRestApiContext } from '@/Interface';
import { get } from '@/utils/apiUtils';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { get } from '@n8n/rest-api-client';
import type {
WorkflowHistory,
WorkflowVersion,

View File

@@ -1,6 +1,7 @@
import type { TransferWorkflowBodyDto } from '@n8n/api-types';
import type { IRestApiContext, IShareWorkflowsPayload, IWorkflowsShareResponse } from '@/Interface';
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { IShareWorkflowsPayload, IWorkflowsShareResponse } from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type { IDataObject } from 'n8n-workflow';
export async function setWorkflowSharedWith(

View File

@@ -4,19 +4,19 @@ import type {
FolderTreeResponseItem,
IExecutionResponse,
IExecutionsCurrentSummaryExtended,
IRestApiContext,
IUsedCredential,
IWorkflowDb,
NewWorkflowResponse,
WorkflowListResource,
} from '@/Interface';
import type { IRestApiContext } from '@n8n/rest-api-client';
import type {
ExecutionFilters,
ExecutionOptions,
ExecutionSummary,
IDataObject,
} from 'n8n-workflow';
import { getFullApiResponse, makeRestApiRequest } from '@/utils/apiUtils';
import { getFullApiResponse, makeRestApiRequest } from '@n8n/rest-api-client';
export async function getNewWorkflow(context: IRestApiContext, data?: IDataObject) {
const response = await makeRestApiRequest<NewWorkflowResponse>(

View File

@@ -5,7 +5,7 @@ import { STORES } from '@n8n/stores';
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
import { useStorage } from '@/composables/useStorage';
import { useRootStore } from '@n8n/stores/useRootStore';
import { getBecomeCreatorCta } from '@/api/ctas';
import { getBecomeCreatorCta } from '@n8n/rest-api-client/api/ctas';
const LOCAL_STORAGE_KEY = 'N8N_BECOME_TEMPLATE_CREATOR_CTA_DISMISSED_AT';
const RESHOW_DISMISSED_AFTER_DAYS = 30;

View File

@@ -14,7 +14,7 @@ import { useTagsStore } from '@/stores/tags.store';
import { useUIStore } from '@/stores/ui.store';
import { createTestNode, createTestWorkflow } from '@/__tests__/mocks';
import { WEBHOOK_NODE_TYPE, type AssignmentCollectionValue } from 'n8n-workflow';
import * as apiWebhooks from '../api/webhooks';
import * as apiWebhooks from '@n8n/rest-api-client/api/webhooks';
import { mockedStore } from '@/__tests__/utils';
import { nodeTypes } from '@/components/CanvasChat/__test__/data';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';

View File

@@ -69,7 +69,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
import { useProjectsStore } from '@/stores/projects.store';
import { useTagsStore } from '@/stores/tags.store';
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
import { findWebhook } from '../api/webhooks';
import { findWebhook } from '@n8n/rest-api-client/api/webhooks';
export type ResolveParameterOptions = {
targetItem?: TargetItem;

View File

@@ -919,8 +919,6 @@ export const IsInPiPWindowSymbol = 'IsInPipWindow' as unknown as InjectionKey<
>;
/** Auth */
export const BROWSER_ID_STORAGE_KEY = 'n8n-browserId';
export const APP_MODALS_ELEMENT_ID = 'app-modals';
export const AI_NODES_PACKAGE_NAME = '@n8n/n8n-nodes-langchain';

View File

@@ -1,5 +1,5 @@
import { makeRestApiRequest } from '@/utils/apiUtils';
import type { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import type { IRestApiContext } from '@n8n/rest-api-client';
import type {
InsightsSummary,
InsightsByTime,

View File

@@ -1,7 +1,7 @@
import type * as Sentry from '@sentry/vue';
import { beforeSend } from '@/plugins/sentry';
import { AxiosError } from 'axios';
import { ResponseError } from '@/utils/apiUtils';
import { ResponseError } from '@n8n/rest-api-client';
function createErrorEvent(): Sentry.ErrorEvent {
return {} as Sentry.ErrorEvent;

View File

@@ -1,6 +1,6 @@
import type { Plugin } from 'vue';
import { AxiosError } from 'axios';
import { ResponseError } from '@/utils/apiUtils';
import { ResponseError } from '@n8n/rest-api-client';
import * as Sentry from '@sentry/vue';
const ignoredErrors = [

View File

@@ -2,7 +2,7 @@ import { STORES } from '@n8n/stores';
import { defineStore } from 'pinia';
import { useRootStore } from '@n8n/stores/useRootStore';
import * as publicApiApi from '@/api/api-keys';
import * as publicApiApi from '@n8n/rest-api-client/api/api-keys';
import { computed, ref } from 'vue';
import type { ApiKey, CreateApiKeyRequestDto, UpdateApiKeyRequestDto } from '@n8n/api-types';
import type { ApiKeyScope } from '@n8n/permissions';

View File

@@ -1,4 +1,4 @@
import * as communityNodesApi from '@/api/communityNodes';
import * as communityNodesApi from '@n8n/rest-api-client/api/communityNodes';
import { getAvailableCommunityPackageCount } from '@/api/settings';
import { defineStore } from 'pinia';
import { useRootStore } from '@n8n/stores/useRootStore';

View File

@@ -13,7 +13,7 @@ import { EnterpriseEditionFeature } from '@/constants';
import { STORES } from '@n8n/stores';
import { i18n } from '@n8n/i18n';
import type { ProjectSharingData } from '@/types/projects.types';
import { makeRestApiRequest } from '@/utils/apiUtils';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import { getAppNameFromCredType } from '@/utils/nodeTypesUtils';
import { splitName } from '@/utils/projects.utils';
import { isEmpty, isPresent } from '@/utils/typesUtils';

View File

@@ -4,7 +4,7 @@ import { setActivePinia, createPinia } from 'pinia';
import type { ExecutionSummaryWithScopes } from '@/Interface';
import { useExecutionsStore } from '@/stores/executions.store';
vi.mock('@/utils/apiUtils', () => ({
vi.mock('@n8n/rest-api-client', () => ({
makeRestApiRequest: vi.fn(),
}));

View File

@@ -12,7 +12,8 @@ import type {
IExecutionsStopData,
} from '@/Interface';
import { useRootStore } from '@n8n/stores/useRootStore';
import { makeRestApiRequest, unflattenExecutionData } from '@/utils/apiUtils';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import { unflattenExecutionData } from '@/utils/executionUtils';
import { executionFilterToQueryFilter, getDefaultExecutionFilters } from '@/utils/executionUtils';
import { useProjectsStore } from '@/stores/projects.store';
import { useSettingsStore } from '@/stores/settings.store';

View File

@@ -7,7 +7,7 @@ import {
hasDestinationId,
saveDestinationToDb,
sendTestMessageToDestination,
} from '@/api/eventbus.ee';
} from '@n8n/rest-api-client/api/eventbus.ee';
import { useRootStore } from '@n8n/stores/useRootStore';
import { ref } from 'vue';

View File

@@ -5,7 +5,7 @@ import type {
ResourceLocatorRequestDto,
ResourceMapperFieldsRequestDto,
} from '@n8n/api-types';
import * as nodeTypesApi from '@/api/nodeTypes';
import * as nodeTypesApi from '@n8n/rest-api-client/api/nodeTypes';
import { HTTP_REQUEST_NODE_TYPE, CREDENTIAL_ONLY_HTTP_NODE_VERSION } from '@/constants';
import { STORES } from '@n8n/stores';
import type { NodeTypesByTypeNameAndVersion } from '@/Interface';

View File

@@ -16,7 +16,7 @@ vi.mock('@/stores/ui.store', () => ({
})),
}));
vi.mock('@/api/npsSurvey', () => ({
vi.mock('@n8n/rest-api-client/api/npsSurvey', () => ({
updateNpsSurveyState,
}));

View File

@@ -11,7 +11,7 @@ import {
import { useRootStore } from '@n8n/stores/useRootStore';
import type { IUserSettings, NpsSurveyState } from 'n8n-workflow';
import { useSettingsStore } from './settings.store';
import { updateNpsSurveyState } from '@/api/npsSurvey';
import { updateNpsSurveyState } from '@n8n/rest-api-client/api/npsSurvey';
import type { IN8nPrompts } from '@/Interface';
import { getPromptsData } from '@/api/settings';
import { assert } from '@n8n/utils/assert';

View File

@@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
import type { WorkerStatus } from '@n8n/api-types';
import { useRootStore } from '@n8n/stores/useRootStore';
import { sendGetWorkerStatus } from '../api/orchestration';
import { sendGetWorkerStatus } from '@n8n/rest-api-client/api/orchestration';
export const WORKER_HISTORY_LENGTH = 100;
const STALE_SECONDS = 120 * 1000;

View File

@@ -1,5 +1,5 @@
import { useRolesStore } from '@/stores/roles.store';
import * as rolesApi from '@/api/roles.api';
import * as rolesApi from '@n8n/rest-api-client/api/roles';
import { createPinia, setActivePinia } from 'pinia';
let rolesStore: ReturnType<typeof useRolesStore>;

View File

@@ -1,7 +1,7 @@
import type { ProjectRole, AllRolesMap } from '@n8n/permissions';
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import * as rolesApi from '@/api/roles.api';
import * as rolesApi from '@n8n/rest-api-client/api/roles';
import { useRootStore } from '@n8n/stores/useRootStore';
export const useRolesStore = defineStore('roles', () => {

View File

@@ -17,7 +17,7 @@ vi.mock('@/api/settings', () => ({
getSettings,
}));
vi.mock('@/api/events', () => ({
vi.mock('@n8n/rest-api-client/api/events', () => ({
sessionStarted,
}));

View File

@@ -2,7 +2,7 @@ import { computed, ref } from 'vue';
import Bowser from 'bowser';
import type { IUserManagementSettings, FrontendSettings } from '@n8n/api-types';
import * as eventsApi from '@/api/events';
import * as eventsApi from '@n8n/rest-api-client/api/events';
import * as ldapApi from '@/api/ldap';
import * as settingsApi from '@/api/settings';
import { testHealthEndpoint } from '@/api/templates';
@@ -19,7 +19,7 @@ import { useRootStore } from '@n8n/stores/useRootStore';
import { useUIStore } from './ui.store';
import { useUsersStore } from './users.store';
import { useVersionsStore } from './versions.store';
import { makeRestApiRequest } from '@/utils/apiUtils';
import { makeRestApiRequest } from '@n8n/rest-api-client';
import { useToast } from '@/composables/useToast';
import { useI18n } from '@n8n/i18n';
import { useLocalStorage } from '@vueuse/core';

View File

@@ -59,7 +59,7 @@ import { useRootStore } from '@n8n/stores/useRootStore';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useUsersStore } from '@/stores/users.store';
import { dismissBannerPermanently } from '@/api/ui';
import { dismissBannerPermanently } from '@n8n/rest-api-client';
import type { BannerName } from '@n8n/api-types';
import {
addThemeToBody,

View File

@@ -6,7 +6,8 @@ import type {
} from '@n8n/api-types';
import type { UpdateGlobalRolePayload } from '@/api/users';
import * as usersApi from '@/api/users';
import { BROWSER_ID_STORAGE_KEY, PERSONALIZATION_MODAL_KEY, ROLE } from '@/constants';
import { BROWSER_ID_STORAGE_KEY } from '@n8n/constants';
import { PERSONALIZATION_MODAL_KEY, ROLE } from '@/constants';
import { STORES } from '@n8n/stores';
import type {
Cloud,
@@ -22,7 +23,7 @@ import { useRootStore } from '@n8n/stores/useRootStore';
import { usePostHog } from './posthog.store';
import { useUIStore } from './ui.store';
import { useCloudPlanStore } from './cloudPlan.store';
import * as mfaApi from '@/api/mfa';
import * as mfaApi from '@n8n/rest-api-client/api/mfa';
import * as cloudApi from '@/api/cloudPlans';
import { useRBACStore } from '@/stores/rbac.store';
import type { Scope } from '@n8n/permissions';

View File

@@ -27,7 +27,7 @@ import { flushPromises } from '@vue/test-utils';
import { useNDVStore } from '@/stores/ndv.store';
import { mock } from 'vitest-mock-extended';
import { mockedStore, type MockedStore } from '@/__tests__/utils';
import * as apiUtils from '@/utils/apiUtils';
import * as apiUtils from '@n8n/rest-api-client';
import { useSettingsStore } from '@/stores/settings.store';
import { useLocalStorage } from '@vueuse/core';
import { ref } from 'vue';

View File

@@ -75,7 +75,8 @@ import { dataPinningEventBus } from '@/event-bus';
import { isObject } from '@/utils/objectUtils';
import { getPairedItemsMapping } from '@/utils/pairedItemUtils';
import { isJsonKeyObject, isEmpty, stringSizeInBytes, isPresent } from '@/utils/typesUtils';
import { makeRestApiRequest, unflattenExecutionData, ResponseError } from '@/utils/apiUtils';
import { makeRestApiRequest, ResponseError } from '@n8n/rest-api-client';
import { unflattenExecutionData } from '@/utils/executionUtils';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { getCredentialOnlyNodeTypeName } from '@/utils/credentialOnlyNodes';

View File

@@ -1,160 +0,0 @@
import { ResponseError, STREAM_SEPERATOR, streamRequest } from './apiUtils';
describe('streamRequest', () => {
it('should stream data from the API endpoint', async () => {
const encoder = new TextEncoder();
const mockResponse = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(`${JSON.stringify({ chunk: 1 })}${STREAM_SEPERATOR}`));
controller.enqueue(encoder.encode(`${JSON.stringify({ chunk: 2 })}${STREAM_SEPERATOR}`));
controller.enqueue(encoder.encode(`${JSON.stringify({ chunk: 3 })}${STREAM_SEPERATOR}`));
controller.close();
},
});
const mockFetch = vi.fn().mockResolvedValue({
ok: true,
body: mockResponse,
});
global.fetch = mockFetch;
const onChunkMock = vi.fn();
const onDoneMock = vi.fn();
const onErrorMock = vi.fn();
await streamRequest(
{
baseUrl: 'https://api.example.com',
pushRef: '',
},
'/data',
{ key: 'value' },
onChunkMock,
onDoneMock,
onErrorMock,
);
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', {
method: 'POST',
body: JSON.stringify({ key: 'value' }),
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'browser-id': expect.stringContaining('-'),
},
});
expect(onChunkMock).toHaveBeenCalledTimes(3);
expect(onChunkMock).toHaveBeenNthCalledWith(1, { chunk: 1 });
expect(onChunkMock).toHaveBeenNthCalledWith(2, { chunk: 2 });
expect(onChunkMock).toHaveBeenNthCalledWith(3, { chunk: 3 });
expect(onDoneMock).toHaveBeenCalledTimes(1);
expect(onErrorMock).not.toHaveBeenCalled();
});
it('should stream error response from the API endpoint', async () => {
const testError = { code: 500, message: 'Error happened' };
const encoder = new TextEncoder();
const mockResponse = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(JSON.stringify(testError)));
controller.close();
},
});
const mockFetch = vi.fn().mockResolvedValue({
ok: false,
body: mockResponse,
});
global.fetch = mockFetch;
const onChunkMock = vi.fn();
const onDoneMock = vi.fn();
const onErrorMock = vi.fn();
await streamRequest(
{
baseUrl: 'https://api.example.com',
pushRef: '',
},
'/data',
{ key: 'value' },
onChunkMock,
onDoneMock,
onErrorMock,
);
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', {
method: 'POST',
body: JSON.stringify({ key: 'value' }),
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'browser-id': expect.stringContaining('-'),
},
});
expect(onChunkMock).not.toHaveBeenCalled();
expect(onErrorMock).toHaveBeenCalledTimes(1);
expect(onErrorMock).toHaveBeenCalledWith(new ResponseError(testError.message));
});
it('should handle broken stream data', async () => {
const encoder = new TextEncoder();
const mockResponse = new ReadableStream({
start(controller) {
controller.enqueue(
encoder.encode(`${JSON.stringify({ chunk: 1 })}${STREAM_SEPERATOR}{"chunk": `),
);
controller.enqueue(encoder.encode(`2}${STREAM_SEPERATOR}{"ch`));
controller.enqueue(encoder.encode('unk":'));
controller.enqueue(encoder.encode(`3}${STREAM_SEPERATOR}`));
controller.close();
},
});
const mockFetch = vi.fn().mockResolvedValue({
ok: true,
body: mockResponse,
});
global.fetch = mockFetch;
const onChunkMock = vi.fn();
const onDoneMock = vi.fn();
const onErrorMock = vi.fn();
await streamRequest(
{
baseUrl: 'https://api.example.com',
pushRef: '',
},
'/data',
{ key: 'value' },
onChunkMock,
onDoneMock,
onErrorMock,
);
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', {
method: 'POST',
body: JSON.stringify({ key: 'value' }),
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'browser-id': expect.stringContaining('-'),
},
});
expect(onChunkMock).toHaveBeenCalledTimes(3);
expect(onChunkMock).toHaveBeenNthCalledWith(1, { chunk: 1 });
expect(onChunkMock).toHaveBeenNthCalledWith(2, { chunk: 2 });
expect(onChunkMock).toHaveBeenNthCalledWith(3, { chunk: 3 });
expect(onDoneMock).toHaveBeenCalledTimes(1);
expect(onErrorMock).not.toHaveBeenCalled();
});
});

View File

@@ -1,311 +0,0 @@
import type { AxiosRequestConfig, Method, RawAxiosRequestHeaders } from 'axios';
import axios from 'axios';
import { ApplicationError, jsonParse, type GenericValue, type IDataObject } from 'n8n-workflow';
import { parse } from 'flatted';
import { assert } from '@n8n/utils/assert';
import { BROWSER_ID_STORAGE_KEY } from '@/constants';
import type { IExecutionFlattedResponse, IExecutionResponse, IRestApiContext } from '@/Interface';
const getBrowserId = () => {
let browserId = localStorage.getItem(BROWSER_ID_STORAGE_KEY);
if (!browserId) {
browserId = crypto.randomUUID();
localStorage.setItem(BROWSER_ID_STORAGE_KEY, browserId);
}
return browserId;
};
export const NO_NETWORK_ERROR_CODE = 999;
export const STREAM_SEPERATOR = '⧉⇋⇋➽⌑⧉§§\n';
export class ResponseError extends ApplicationError {
// The HTTP status code of response
httpStatusCode?: number;
// The error code in the response
errorCode?: number;
// The stack trace of the server
serverStackTrace?: string;
/**
* Creates an instance of ResponseError.
* @param {string} message The error message
* @param {number} [errorCode] The error code which can be used by frontend to identify the actual error
* @param {number} [httpStatusCode] The HTTP status code the response should have
* @param {string} [stack] The stack trace
*/
constructor(
message: string,
options: { errorCode?: number; httpStatusCode?: number; stack?: string } = {},
) {
super(message);
this.name = 'ResponseError';
const { errorCode, httpStatusCode, stack } = options;
if (errorCode) {
this.errorCode = errorCode;
}
if (httpStatusCode) {
this.httpStatusCode = httpStatusCode;
}
if (stack) {
this.serverStackTrace = stack;
}
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const legacyParamSerializer = (params: Record<string, any>) =>
Object.keys(params)
.filter((key) => params[key] !== undefined)
.map((key) => {
if (Array.isArray(params[key])) {
return params[key].map((v: string) => `${key}[]=${encodeURIComponent(v)}`).join('&');
}
if (typeof params[key] === 'object') {
params[key] = JSON.stringify(params[key]);
}
return `${key}=${encodeURIComponent(params[key])}`;
})
.join('&');
export async function request(config: {
method: Method;
baseURL: string;
endpoint: string;
headers?: RawAxiosRequestHeaders;
data?: GenericValue | GenericValue[];
withCredentials?: boolean;
}) {
const { method, baseURL, endpoint, headers, data } = config;
const options: AxiosRequestConfig = {
method,
url: endpoint,
baseURL,
headers: headers ?? {},
};
if (baseURL.startsWith('/')) {
options.headers!['browser-id'] = getBrowserId();
}
if (
import.meta.env.NODE_ENV !== 'production' &&
!baseURL.includes('api.n8n.io') &&
!baseURL.includes('n8n.cloud')
) {
options.withCredentials = options.withCredentials ?? true;
}
if (['POST', 'PATCH', 'PUT'].includes(method)) {
options.data = data;
} else if (data) {
options.params = data;
options.paramsSerializer = legacyParamSerializer;
}
try {
const response = await axios.request(options);
return response.data;
} catch (error) {
if (error.message === 'Network Error') {
throw new ResponseError("Can't connect to n8n.", {
errorCode: NO_NETWORK_ERROR_CODE,
});
}
const errorResponseData = error.response?.data;
if (errorResponseData?.message !== undefined) {
if (errorResponseData.name === 'NodeApiError') {
errorResponseData.httpStatusCode = error.response.status;
throw errorResponseData;
}
throw new ResponseError(errorResponseData.message, {
errorCode: errorResponseData.code,
httpStatusCode: error.response.status,
stack: errorResponseData.stack,
});
}
throw error;
}
}
/**
* Sends a request to the API and returns the response without extracting the data key.
* @param context Rest API context
* @param method HTTP method
* @param endpoint relative path to the API endpoint
* @param data request data
* @returns data and total count
*/
export async function getFullApiResponse<T>(
context: IRestApiContext,
method: Method,
endpoint: string,
data?: GenericValue | GenericValue[],
) {
const response = await request({
method,
baseURL: context.baseUrl,
endpoint,
headers: { 'push-ref': context.pushRef },
data,
});
return response as { count: number; data: T };
}
export async function makeRestApiRequest<T>(
context: IRestApiContext,
method: Method,
endpoint: string,
data?: GenericValue | GenericValue[],
) {
const response = await request({
method,
baseURL: context.baseUrl,
endpoint,
headers: { 'push-ref': context.pushRef },
data,
});
// @ts-ignore all cli rest api endpoints return data wrapped in `data` key
return response.data as T;
}
export async function get(
baseURL: string,
endpoint: string,
params?: IDataObject,
headers?: RawAxiosRequestHeaders,
) {
return await request({ method: 'GET', baseURL, endpoint, headers, data: params });
}
export async function post(
baseURL: string,
endpoint: string,
params?: IDataObject,
headers?: RawAxiosRequestHeaders,
) {
return await request({ method: 'POST', baseURL, endpoint, headers, data: params });
}
export async function patch(
baseURL: string,
endpoint: string,
params?: IDataObject,
headers?: RawAxiosRequestHeaders,
) {
return await request({ method: 'PATCH', baseURL, endpoint, headers, data: params });
}
/**
* Unflattens the Execution data.
*
* @param {IExecutionFlattedResponse} fullExecutionData The data to unflatten
*/
export function unflattenExecutionData(fullExecutionData: IExecutionFlattedResponse) {
// Unflatten the data
const returnData: IExecutionResponse = {
...fullExecutionData,
workflowData: fullExecutionData.workflowData,
data: parse(fullExecutionData.data),
};
returnData.finished = returnData.finished ? returnData.finished : false;
if (fullExecutionData.id) {
returnData.id = fullExecutionData.id;
}
return returnData;
}
export async function streamRequest<T extends object>(
context: IRestApiContext,
apiEndpoint: string,
payload: object,
onChunk?: (chunk: T) => void,
onDone?: () => void,
onError?: (e: Error) => void,
separator = STREAM_SEPERATOR,
): Promise<void> {
const headers: Record<string, string> = {
'browser-id': getBrowserId(),
'Content-Type': 'application/json',
};
const assistantRequest: RequestInit = {
headers,
method: 'POST',
credentials: 'include',
body: JSON.stringify(payload),
};
try {
const response = await fetch(`${context.baseUrl}${apiEndpoint}`, assistantRequest);
if (response.body) {
// Handle the streaming response
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
async function readStream() {
const { done, value } = await reader.read();
if (done) {
onDone?.();
return;
}
const chunk = decoder.decode(value);
buffer += chunk;
const splitChunks = buffer.split(separator);
buffer = '';
for (const splitChunk of splitChunks) {
if (splitChunk) {
let data: T;
try {
data = jsonParse<T>(splitChunk, { errorMessage: 'Invalid json' });
} catch (e) {
// incomplete json. append to buffer to complete
buffer += splitChunk;
continue;
}
try {
if (response.ok) {
// Call chunk callback if request was successful
onChunk?.(data);
} else {
// Otherwise, call error callback
const message = 'message' in data ? data.message : response.statusText;
onError?.(
new ResponseError(String(message), {
httpStatusCode: response.status,
}),
);
}
} catch (e: unknown) {
if (e instanceof Error) {
onError?.(e);
}
}
}
}
await readStream();
}
// Start reading the stream
await readStream();
} else if (onError) {
onError(new Error(response.statusText));
}
} catch (e: unknown) {
assert(e instanceof Error);
onError?.(e);
}
}

View File

@@ -8,7 +8,13 @@ import type {
IRunData,
ExecutionError,
} from 'n8n-workflow';
import type { ExecutionFilterType, ExecutionsQueryFilter, INodeUi } from '@/Interface';
import type {
ExecutionFilterType,
ExecutionsQueryFilter,
IExecutionFlattedResponse,
IExecutionResponse,
INodeUi,
} from '@/Interface';
import { isEmpty } from '@/utils/typesUtils';
import { FORM_NODE_TYPE, FORM_TRIGGER_NODE_TYPE, GITHUB_NODE_TYPE } from '../constants';
import { useWorkflowsStore } from '@/stores/workflows.store';
@@ -16,6 +22,7 @@ import { useRootStore } from '@n8n/stores/useRootStore';
import { i18n } from '@n8n/i18n';
import { h } from 'vue';
import NodeExecutionErrorMessage from '@/components/NodeExecutionErrorMessage.vue';
import { parse } from 'flatted';
export function getDefaultExecutionFilters(): ExecutionFilterType {
return {
@@ -356,3 +363,25 @@ export function getExecutionErrorToastConfiguration({
message,
};
}
/**
* Unflattens the Execution data.
*
* @param {IExecutionFlattedResponse} fullExecutionData The data to unflatten
*/
export function unflattenExecutionData(fullExecutionData: IExecutionFlattedResponse) {
// Unflatten the data
const returnData: IExecutionResponse = {
...fullExecutionData,
workflowData: fullExecutionData.workflowData,
data: parse(fullExecutionData.data),
};
returnData.finished = returnData.finished ? returnData.finished : false;
if (fullExecutionData.id) {
returnData.id = fullExecutionData.id;
}
return returnData;
}

View File

@@ -6,7 +6,7 @@ import { useI18n } from '@n8n/i18n';
import type { ExecutionFilterType, IWorkflowDb } from '@/Interface';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { NO_NETWORK_ERROR_CODE } from '@/utils/apiUtils';
import { NO_NETWORK_ERROR_CODE } from '@n8n/rest-api-client';
import { useToast } from '@/composables/useToast';
import { NEW_WORKFLOW_ID, PLACEHOLDER_EMPTY_WORKFLOW_ID, VIEWS } from '@/constants';
import { useRoute, useRouter } from 'vue-router';