refactor(editor): Remove the restApi mixin (#6065)

*  Removing the `makeApiRequest` method from `restAPI` mixin, removing the mixing from the App component
*  Removing `restApi` mixin
* 👕 Fixing lint errors
* ✔️ Fixing execution list unit tests and merge bug in workflowRun mixin
* 🐛 Added missing useStore
This commit is contained in:
Milorad FIlipović 2023-04-24 10:50:49 +02:00 committed by GitHub
parent 4bd55f7a1e
commit 59db96771e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 905 additions and 734 deletions

View file

@ -37,7 +37,6 @@ import { showMessage } from '@/mixins/showMessage';
import { userHelpers } from '@/mixins/userHelpers'; import { userHelpers } from '@/mixins/userHelpers';
import { loadLanguage } from './plugins/i18n'; import { loadLanguage } from './plugins/i18n';
import useGlobalLinkActions from '@/composables/useGlobalLinkActions'; import useGlobalLinkActions from '@/composables/useGlobalLinkActions';
import { restApi } from '@/mixins/restApi';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUIStore } from './stores/ui'; import { useUIStore } from './stores/ui';
import { useSettingsStore } from './stores/settings'; import { useSettingsStore } from './stores/settings';
@ -49,7 +48,7 @@ import { useHistoryHelper } from '@/composables/useHistoryHelper';
import { newVersions } from '@/mixins/newVersions'; import { newVersions } from '@/mixins/newVersions';
import { useRoute } from 'vue-router/composables'; import { useRoute } from 'vue-router/composables';
export default mixins(newVersions, showMessage, userHelpers, restApi).extend({ export default mixins(newVersions, showMessage, userHelpers).extend({
name: 'App', name: 'App',
components: { components: {
LoadingView, LoadingView,

View file

@ -139,43 +139,6 @@ export interface IExternalHooks {
run(eventName: string, metadata?: IDataObject): Promise<void>; run(eventName: string, metadata?: IDataObject): Promise<void>;
} }
/**
* @deprecated Do not add methods to this interface.
*/
export interface IRestApi {
getActiveWorkflows(): Promise<string[]>;
getActivationError(id: string): Promise<IActivationError | undefined>;
getCurrentExecutions(filter: ExecutionsQueryFilter): Promise<IExecutionsCurrentSummaryExtended[]>;
getPastExecutions(
filter: ExecutionsQueryFilter,
limit: number,
lastId?: string,
firstId?: string,
): Promise<IExecutionsListResponse>;
stopCurrentExecution(executionId: string): Promise<IExecutionsStopData>;
makeRestApiRequest(method: string, endpoint: string, data?: any): Promise<any>;
getCredentialTranslation(credentialType: string): Promise<object>;
removeTestWebhook(workflowId: string): Promise<boolean>;
runWorkflow(runData: IStartRunData): Promise<IExecutionPushResponse>;
createNewWorkflow(sendData: IWorkflowDataUpdate): Promise<IWorkflowDb>;
updateWorkflow(id: string, data: IWorkflowDataUpdate, forceSave?: boolean): Promise<IWorkflowDb>;
deleteWorkflow(name: string): Promise<void>;
getWorkflow(id: string): Promise<IWorkflowDb>;
getWorkflows(filter?: object): Promise<IWorkflowShortResponse[]>;
getWorkflowFromUrl(url: string): Promise<IWorkflowDb>;
getExecution(id: string): Promise<IExecutionResponse | undefined>;
deleteExecutions(sendData: IExecutionDeleteFilter): Promise<void>;
retryExecution(id: string, loadWorkflow?: boolean): Promise<boolean>;
getTimezones(): Promise<IDataObject>;
getBinaryUrl(
dataPath: string,
mode: 'view' | 'download',
fileName?: string,
mimeType?: string,
): string;
getExecutionEvents(id: string): Promise<IAbstractEventMessage[]>;
}
export interface INodeTranslationHeaders { export interface INodeTranslationHeaders {
data: { data: {
[key: string]: { [key: string]: {

View file

@ -26,11 +26,10 @@ import BinaryDataDisplayEmbed from '@/components/BinaryDataDisplayEmbed.vue';
import { nodeHelpers } from '@/mixins/nodeHelpers'; import { nodeHelpers } from '@/mixins/nodeHelpers';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { restApi } from '@/mixins/restApi';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
export default mixins(nodeHelpers, restApi).extend({ export default mixins(nodeHelpers).extend({
name: 'BinaryDataDisplay', name: 'BinaryDataDisplay',
components: { components: {
BinaryDataDisplayEmbed, BinaryDataDisplayEmbed,

View file

@ -19,13 +19,14 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import mixins from 'vue-typed-mixins';
import { restApi } from '@/mixins/restApi';
import { IBinaryData, jsonParse } from 'n8n-workflow'; import { IBinaryData, jsonParse } from 'n8n-workflow';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import VueJsonPretty from 'vue-json-pretty'; import VueJsonPretty from 'vue-json-pretty';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores';
import Vue from 'vue';
export default mixins(restApi).extend({ export default Vue.extend({
name: 'BinaryDataDisplayEmbed', name: 'BinaryDataDisplayEmbed',
components: { components: {
VueJsonPretty, VueJsonPretty,
@ -44,6 +45,9 @@ export default mixins(restApi).extend({
jsonData: '', jsonData: '',
}; };
}, },
computed: {
...mapStores(useWorkflowsStore),
},
async mounted() { async mounted() {
const { id, data, fileName, fileType, mimeType } = (this.binaryData || {}) as IBinaryData; const { id, data, fileName, fileType, mimeType } = (this.binaryData || {}) as IBinaryData;
const isJSONData = fileType === 'json'; const isJSONData = fileType === 'json';
@ -56,7 +60,7 @@ export default mixins(restApi).extend({
} }
} else { } else {
try { try {
const binaryUrl = this.restApi().getBinaryUrl(id, 'view', fileName, mimeType); const binaryUrl = this.workflowsStore.getBinaryUrl(id, 'view', fileName, mimeType);
if (isJSONData) { if (isJSONData) {
this.jsonData = await (await fetch(binaryUrl)).json(); this.jsonData = await (await fetch(binaryUrl)).json();
} else { } else {

View file

@ -135,9 +135,7 @@ import Banner from '../Banner.vue';
import CopyInput from '../CopyInput.vue'; import CopyInput from '../CopyInput.vue';
import CredentialInputs from './CredentialInputs.vue'; import CredentialInputs from './CredentialInputs.vue';
import OauthButton from './OauthButton.vue'; import OauthButton from './OauthButton.vue';
import { restApi } from '@/mixins/restApi';
import { addCredentialTranslation } from '@/plugins/i18n'; import { addCredentialTranslation } from '@/plugins/i18n';
import mixins from 'vue-typed-mixins';
import { BUILTIN_CREDENTIALS_DOCS_URL, DOCS_DOMAIN, EnterpriseEditionFeature } from '@/constants'; import { BUILTIN_CREDENTIALS_DOCS_URL, DOCS_DOMAIN, EnterpriseEditionFeature } from '@/constants';
import { IPermissions } from '@/permissions'; import { IPermissions } from '@/permissions';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
@ -147,12 +145,12 @@ import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv'; import { useNDVStore } from '@/stores/ndv';
import { useCredentialsStore } from '@/stores/credentials'; import { useCredentialsStore } from '@/stores/credentials';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { ICredentialsResponse, IUpdateInformation, NodeAuthenticationOption } from '@/Interface'; import { ICredentialsResponse } from '@/Interface';
import ParameterInputFull from '@/components/ParameterInputFull.vue';
import AuthTypeSelector from '@/components/CredentialEdit/AuthTypeSelector.vue'; import AuthTypeSelector from '@/components/CredentialEdit/AuthTypeSelector.vue';
import GoogleAuthButton from './GoogleAuthButton.vue'; import GoogleAuthButton from './GoogleAuthButton.vue';
import Vue from 'vue';
export default mixins(restApi).extend({ export default Vue.extend({
name: 'CredentialConfig', name: 'CredentialConfig',
components: { components: {
AuthTypeSelector, AuthTypeSelector,
@ -160,7 +158,6 @@ export default mixins(restApi).extend({
CopyInput, CopyInput,
CredentialInputs, CredentialInputs,
OauthButton, OauthButton,
ParameterInputFull,
GoogleAuthButton, GoogleAuthButton,
}, },
props: { props: {
@ -226,7 +223,9 @@ export default mixins(restApi).extend({
if (this.$locale.exists(key)) return; if (this.$locale.exists(key)) return;
const credTranslation = await this.restApi().getCredentialTranslation(this.credentialType.name); const credTranslation = await this.credentialsStore.getCredentialTranslation(
this.credentialType.name,
);
addCredentialTranslation( addCredentialTranslation(
{ [this.credentialType.name]: credTranslation }, { [this.credentialType.name]: credTranslation },

View file

@ -56,7 +56,6 @@ import { workflowHelpers } from '@/mixins/workflowHelpers';
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
import TagsDropdown from '@/components/TagsDropdown.vue'; import TagsDropdown from '@/components/TagsDropdown.vue';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
import { restApi } from '@/mixins/restApi';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
@ -64,8 +63,9 @@ import { IWorkflowDataUpdate } from '@/Interface';
import { getWorkflowPermissions, IPermissions } from '@/permissions'; import { getWorkflowPermissions, IPermissions } from '@/permissions';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { createEventBus } from '@/event-bus'; import { createEventBus } from '@/event-bus';
import { useCredentialsStore } from '@/stores';
export default mixins(showMessage, workflowHelpers, restApi).extend({ export default mixins(showMessage, workflowHelpers).extend({
components: { TagsDropdown, Modal }, components: { TagsDropdown, Modal },
name: 'DuplicateWorkflow', name: 'DuplicateWorkflow',
props: ['modalName', 'isActive', 'data'], props: ['modalName', 'isActive', 'data'],
@ -87,7 +87,7 @@ export default mixins(showMessage, workflowHelpers, restApi).extend({
this.$nextTick(() => this.focusOnNameInput()); this.$nextTick(() => this.focusOnNameInput());
}, },
computed: { computed: {
...mapStores(useUsersStore, useSettingsStore, useWorkflowsStore), ...mapStores(useCredentialsStore, useUsersStore, useSettingsStore, useWorkflowsStore),
workflowPermissions(): IPermissions { workflowPermissions(): IPermissions {
const isEmptyWorkflow = this.data.id === PLACEHOLDER_EMPTY_WORKFLOW_ID; const isEmptyWorkflow = this.data.id === PLACEHOLDER_EMPTY_WORKFLOW_ID;
const isCurrentWorkflowEmpty = const isCurrentWorkflowEmpty =
@ -150,7 +150,7 @@ export default mixins(showMessage, workflowHelpers, restApi).extend({
let workflowToUpdate: IWorkflowDataUpdate | undefined; let workflowToUpdate: IWorkflowDataUpdate | undefined;
if (currentWorkflowId !== PLACEHOLDER_EMPTY_WORKFLOW_ID) { if (currentWorkflowId !== PLACEHOLDER_EMPTY_WORKFLOW_ID) {
const { createdAt, updatedAt, usedCredentials, ...workflow } = const { createdAt, updatedAt, usedCredentials, ...workflow } =
await this.restApi().getWorkflow(this.data.id); await this.workflowsStore.fetchWorkflow(this.data.id);
workflowToUpdate = workflow; workflowToUpdate = workflow;
this.removeForeignCredentialsFromWorkflow( this.removeForeignCredentialsFromWorkflow(

File diff suppressed because it is too large Load diff

View file

@ -86,10 +86,9 @@ import mixins from 'vue-typed-mixins';
import { executionHelpers, IExecutionUIData } from '@/mixins/executionsHelpers'; import { executionHelpers, IExecutionUIData } from '@/mixins/executionsHelpers';
import { VIEWS } from '@/constants'; import { VIEWS } from '@/constants';
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
import { restApi } from '@/mixins/restApi';
import ExecutionTime from '@/components/ExecutionTime.vue'; import ExecutionTime from '@/components/ExecutionTime.vue';
export default mixins(executionHelpers, showMessage, restApi).extend({ export default mixins(executionHelpers, showMessage).extend({
name: 'execution-card', name: 'execution-card',
components: { components: {
ExecutionTime, ExecutionTime,

View file

@ -127,7 +127,6 @@
<script lang="ts"> <script lang="ts">
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { restApi } from '@/mixins/restApi';
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
import WorkflowPreview from '@/components/WorkflowPreview.vue'; import WorkflowPreview from '@/components/WorkflowPreview.vue';
import { executionHelpers, IExecutionUIData } from '@/mixins/executionsHelpers'; import { executionHelpers, IExecutionUIData } from '@/mixins/executionsHelpers';
@ -135,11 +134,10 @@ import { VIEWS } from '@/constants';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { Dropdown as ElDropdown } from 'element-ui'; import { Dropdown as ElDropdown } from 'element-ui';
import { IAbstractEventMessage } from 'n8n-workflow';
type RetryDropdownRef = InstanceType<typeof ElDropdown> & { hide: () => void }; type RetryDropdownRef = InstanceType<typeof ElDropdown> & { hide: () => void };
export default mixins(restApi, showMessage, executionHelpers).extend({ export default mixins(showMessage, executionHelpers).extend({
name: 'execution-preview', name: 'execution-preview',
components: { components: {
ElDropdown, ElDropdown,

View file

@ -49,7 +49,6 @@ import {
NodeHelpers, NodeHelpers,
} from 'n8n-workflow'; } from 'n8n-workflow';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { restApi } from '@/mixins/restApi';
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { Route } from 'vue-router'; import { Route } from 'vue-router';
@ -71,13 +70,7 @@ const MAX_LOADING_ATTEMPTS = 5;
// Number of executions fetched on each page // Number of executions fetched on each page
const LOAD_MORE_PAGE_SIZE = 100; const LOAD_MORE_PAGE_SIZE = 100;
export default mixins( export default mixins(showMessage, executionHelpers, debounceHelper, workflowHelpers).extend({
restApi,
showMessage,
executionHelpers,
debounceHelper,
workflowHelpers,
).extend({
name: 'executions-list', name: 'executions-list',
components: { components: {
ExecutionsSidebar, ExecutionsSidebar,
@ -225,7 +218,7 @@ export default mixins(
let data: IExecutionsListResponse; let data: IExecutionsListResponse;
try { try {
data = await this.restApi().getPastExecutions(this.requestFilter, limit, lastId); data = await this.workflowsStore.getPastExecutions(this.requestFilter, limit, lastId);
} catch (error) { } catch (error) {
this.loadingMore = false; this.loadingMore = false;
this.$showError(error, this.$locale.baseText('executionsList.showError.loadMore.title')); this.$showError(error, this.$locale.baseText('executionsList.showError.loadMore.title'));
@ -260,7 +253,7 @@ export default mixins(
this.executions[executionIndex - 1] || this.executions[executionIndex - 1] ||
this.executions[0]; this.executions[0];
await this.restApi().deleteExecutions({ ids: [this.$route.params.executionId] }); await this.workflowsStore.deleteExecutions({ ids: [this.$route.params.executionId] });
if (this.temporaryExecution?.id === this.$route.params.executionId) { if (this.temporaryExecution?.id === this.$route.params.executionId) {
this.temporaryExecution = null; this.temporaryExecution = null;
} }
@ -300,7 +293,7 @@ export default mixins(
const activeExecutionId = this.$route.params.executionId; const activeExecutionId = this.$route.params.executionId;
try { try {
await this.restApi().stopCurrentExecution(activeExecutionId); await this.workflowsStore.stopCurrentExecution(activeExecutionId);
this.$showMessage({ this.$showMessage({
title: this.$locale.baseText('executionsList.showMessage.stopExecution.title'), title: this.$locale.baseText('executionsList.showMessage.stopExecution.title'),
@ -498,7 +491,7 @@ export default mixins(
let data: IWorkflowDb | undefined; let data: IWorkflowDb | undefined;
try { try {
data = await this.restApi().getWorkflow(workflowId); data = await this.workflowsStore.fetchWorkflow(workflowId);
} catch (error) { } catch (error) {
this.$showError(error, this.$locale.baseText('nodeView.showError.openWorkflow.title')); this.$showError(error, this.$locale.baseText('nodeView.showError.openWorkflow.title'));
return; return;
@ -644,7 +637,7 @@ export default mixins(
} }
}, },
async loadActiveWorkflows(): Promise<void> { async loadActiveWorkflows(): Promise<void> {
this.workflowsStore.activeWorkflows = await this.restApi().getActiveWorkflows(); await this.workflowsStore.fetchActiveWorkflows();
}, },
async onRetryExecution(payload: { execution: IExecutionsSummary; command: string }) { async onRetryExecution(payload: { execution: IExecutionsSummary; command: string }) {
const loadWorkflow = payload.command === 'current-workflow'; const loadWorkflow = payload.command === 'current-workflow';
@ -665,7 +658,10 @@ export default mixins(
}, },
async retryExecution(execution: IExecutionsSummary, loadWorkflow?: boolean) { async retryExecution(execution: IExecutionsSummary, loadWorkflow?: boolean) {
try { try {
const retrySuccessful = await this.restApi().retryExecution(execution.id, loadWorkflow); const retrySuccessful = await this.workflowsStore.retryExecution(
execution.id,
loadWorkflow,
);
if (retrySuccessful === true) { if (retrySuccessful === true) {
this.$showMessage({ this.$showMessage({

View file

@ -509,7 +509,7 @@ export default mixins(workflowHelpers).extend({
} }
try { try {
await this.restApi().deleteWorkflow(this.currentWorkflowId); await this.workflowsStore.deleteWorkflowAPI(this.currentWorkflowId);
} catch (error) { } catch (error) {
this.$showError( this.$showError(
error, error,

View file

@ -97,7 +97,6 @@ import GiftNotificationIcon from './GiftNotificationIcon.vue';
import WorkflowSettings from '@/components/WorkflowSettings.vue'; import WorkflowSettings from '@/components/WorkflowSettings.vue';
import { genericHelpers } from '@/mixins/genericHelpers'; import { genericHelpers } from '@/mixins/genericHelpers';
import { restApi } from '@/mixins/restApi';
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
import { workflowHelpers } from '@/mixins/workflowHelpers'; import { workflowHelpers } from '@/mixins/workflowHelpers';
import { workflowRun } from '@/mixins/workflowRun'; import { workflowRun } from '@/mixins/workflowRun';
@ -118,7 +117,6 @@ import { isNavigationFailure } from 'vue-router';
export default mixins( export default mixins(
genericHelpers, genericHelpers,
restApi,
showMessage, showMessage,
workflowHelpers, workflowHelpers,
workflowRun, workflowRun,

View file

@ -98,7 +98,6 @@
<script lang="ts"> <script lang="ts">
import { PropType } from 'vue'; import { PropType } from 'vue';
import { restApi } from '@/mixins/restApi';
import { import {
ICredentialsResponse, ICredentialsResponse,
INodeUi, INodeUi,
@ -142,7 +141,7 @@ interface CredentialDropdownOption extends ICredentialsResponse {
typeDisplayName: string; typeDisplayName: string;
} }
export default mixins(genericHelpers, nodeHelpers, restApi, showMessage).extend({ export default mixins(genericHelpers, nodeHelpers, showMessage).extend({
name: 'NodeCredentials', name: 'NodeCredentials',
props: { props: {
readonly: { readonly: {

View file

@ -171,7 +171,7 @@ export default mixins(workflowRun, pinData).extend({
methods: { methods: {
async stopWaitingForWebhook() { async stopWaitingForWebhook() {
try { try {
await this.restApi().removeTestWebhook(this.workflowsStore.workflowId); await this.workflowsStore.removeTestWebhook(this.workflowsStore.workflowId);
} catch (error) { } catch (error) {
this.$showError(error, this.$locale.baseText('ndv.execute.stopWaitingForWebhook.error')); this.$showError(error, this.$locale.baseText('ndv.execute.stopWaitingForWebhook.error'));
return; return;

View file

@ -1251,7 +1251,7 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers, pinData).exten
const { id, data, fileName, fileExtension, mimeType } = this.binaryData[index][key]; const { id, data, fileName, fileExtension, mimeType } = this.binaryData[index][key];
if (id) { if (id) {
const url = this.restApi().getBinaryUrl(id, 'download', fileName, mimeType); const url = this.workflowsStore.getBinaryUrl(id, 'download', fileName, mimeType);
saveAs(url, [fileName, fileExtension].join('.')); saveAs(url, [fileName, fileExtension].join('.'));
return; return;
} else { } else {

View file

@ -86,7 +86,7 @@ const workflowName = ref('');
onMounted(async () => { onMounted(async () => {
const currentSettings = getCurrentSettings(); const currentSettings = getCurrentSettings();
try { try {
const { name } = await workflowStore.fetchWorkflow( const { name } = await workflowStore.fetchAndSetWorkflow(
currentSettings?.firstSuccessfulWorkflowId ?? '', currentSettings?.firstSuccessfulWorkflowId ?? '',
); );
workflowName.value = name; workflowName.value = name;

View file

@ -105,7 +105,7 @@ export default mixins(showMessage, workflowActivate).extend({
async displayActivationError() { async displayActivationError() {
let errorMessage: string; let errorMessage: string;
try { try {
const errorData = await this.restApi().getActivationError(this.workflowId); const errorData = await this.workflowsStore.getActivationError(this.workflowId);
if (errorData === undefined) { if (errorData === undefined) {
errorMessage = this.$locale.baseText( errorMessage = this.$locale.baseText(

View file

@ -73,7 +73,6 @@ import {
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
import { getWorkflowPermissions, IPermissions } from '@/permissions'; import { getWorkflowPermissions, IPermissions } from '@/permissions';
import dateformat from 'dateformat'; import dateformat from 'dateformat';
import { restApi } from '@/mixins/restApi';
import WorkflowActivator from '@/components/WorkflowActivator.vue'; import WorkflowActivator from '@/components/WorkflowActivator.vue';
import Vue from 'vue'; import Vue from 'vue';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
@ -91,7 +90,7 @@ export const WORKFLOW_LIST_ITEM_ACTIONS = {
DELETE: 'delete', DELETE: 'delete',
}; };
export default mixins(showMessage, restApi).extend({ export default mixins(showMessage).extend({
data() { data() {
return { return {
EnterpriseEditionFeature, EnterpriseEditionFeature,
@ -234,7 +233,7 @@ export default mixins(showMessage, restApi).extend({
} }
try { try {
await this.restApi().deleteWorkflow(this.data.id); await this.workflowsStore.deleteWorkflowAPI(this.data.id);
this.workflowsStore.deleteWorkflow(this.data.id); this.workflowsStore.deleteWorkflow(this.data.id);
} catch (error) { } catch (error) {
this.$showError( this.$showError(

View file

@ -327,7 +327,6 @@
import Vue from 'vue'; import Vue from 'vue';
import { externalHooks } from '@/mixins/externalHooks'; import { externalHooks } from '@/mixins/externalHooks';
import { restApi } from '@/mixins/restApi';
import { genericHelpers } from '@/mixins/genericHelpers'; import { genericHelpers } from '@/mixins/genericHelpers';
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
import { import {
@ -357,7 +356,7 @@ import useWorkflowsEEStore from '@/stores/workflows.ee';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { createEventBus } from '@/event-bus'; import { createEventBus } from '@/event-bus';
export default mixins(externalHooks, genericHelpers, restApi, showMessage).extend({ export default mixins(externalHooks, genericHelpers, showMessage).extend({
name: 'WorkflowSettings', name: 'WorkflowSettings',
components: { components: {
Modal, Modal,
@ -703,7 +702,7 @@ export default mixins(externalHooks, genericHelpers, restApi, showMessage).exten
return; return;
} }
const timezones = await this.restApi().getTimezones(); const timezones = await this.settingsStore.getTimezones();
let defaultTimezoneValue = timezones[this.defaultValues.timezone] as string | undefined; let defaultTimezoneValue = timezones[this.defaultValues.timezone] as string | undefined;
if (defaultTimezoneValue === undefined) { if (defaultTimezoneValue === undefined) {
@ -724,7 +723,7 @@ export default mixins(externalHooks, genericHelpers, restApi, showMessage).exten
} }
}, },
async loadWorkflows() { async loadWorkflows() {
const workflows = await this.restApi().getWorkflows(); const workflows = await this.workflowsStore.fetchAllWorkflows();
workflows.sort((a, b) => { workflows.sort((a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) { if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1; return -1;
@ -789,7 +788,7 @@ export default mixins(externalHooks, genericHelpers, restApi, showMessage).exten
data.versionId = this.workflowsStore.workflowVersionId; data.versionId = this.workflowsStore.workflowVersionId;
try { try {
const workflow = await this.restApi().updateWorkflow(this.$route.params.name, data); const workflow = await this.workflowsStore.updateWorkflow(this.$route.params.name, data);
this.workflowsStore.setWorkflowVersionId(workflow.versionId); this.workflowsStore.setWorkflowVersionId(workflow.versionId);
} catch (error) { } catch (error) {
this.$showError( this.$showError(

View file

@ -448,7 +448,7 @@ export default mixins(showMessage).extend({
this.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID && this.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID &&
!this.workflow.sharedWith?.length // Sharing info already loaded !this.workflow.sharedWith?.length // Sharing info already loaded
) { ) {
await this.workflowsStore.fetchWorkflow(this.workflow.id); await this.workflowsStore.fetchAndSetWorkflow(this.workflow.id);
} }
} }

View file

@ -12,17 +12,21 @@ import { genericHelpers } from '@/mixins/genericHelpers';
import { executionHelpers } from '@/mixins/executionsHelpers'; import { executionHelpers } from '@/mixins/executionsHelpers';
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
import { i18nInstance } from '@/plugins/i18n'; import { i18nInstance } from '@/plugins/i18n';
import type { IWorkflowShortResponse } from '@/Interface'; import type { IWorkflowDb } from '@/Interface';
import type { IExecutionsSummary } from 'n8n-workflow'; import type { IExecutionsSummary } from 'n8n-workflow';
import { waitAllPromises } from '@/__tests__/utils'; import { waitAllPromises } from '@/__tests__/utils';
import { useWorkflowsStore } from '@/stores';
const workflowDataFactory = (): IWorkflowShortResponse => ({ const workflowDataFactory = (): IWorkflowDb => ({
createdAt: faker.date.past().toDateString(), createdAt: faker.date.past().toDateString(),
updatedAt: faker.date.past().toDateString(), updatedAt: faker.date.past().toDateString(),
id: faker.datatype.uuid(), id: faker.datatype.uuid(),
name: faker.datatype.string(), name: faker.datatype.string(),
active: faker.datatype.boolean(), active: faker.datatype.boolean(),
tags: [], tags: [],
nodes: [],
connections: {},
versionId: faker.datatype.number().toString(),
}); });
const executionDataFactory = (): IExecutionsSummary => ({ const executionDataFactory = (): IExecutionsSummary => ({
@ -45,20 +49,6 @@ const executionsData = Array.from({ length: 2 }, () => ({
estimated: false, estimated: false,
})); }));
let getPastExecutionsSpy = vi.fn().mockResolvedValue({ count: 0, results: [], estimated: false });
const mockRestApiMixin = defineComponent({
methods: {
restApi() {
return {
getWorkflows: vi.fn().mockResolvedValue(workflowsData),
getCurrentExecutions: vi.fn().mockResolvedValue([]),
getPastExecutions: getPastExecutionsSpy,
};
},
},
});
const renderOptions = { const renderOptions = {
pinia: createTestingPinia({ pinia: createTestingPinia({
initialState: { initialState: {
@ -83,7 +73,7 @@ const renderOptions = {
}), }),
i18n: i18nInstance, i18n: i18nInstance,
stubs: ['font-awesome-icon'], stubs: ['font-awesome-icon'],
mixins: [externalHooks, genericHelpers, executionHelpers, showMessage, mockRestApiMixin], mixins: [externalHooks, genericHelpers, executionHelpers, showMessage],
}; };
function TelemetryPlugin(vue: typeof Vue): void { function TelemetryPlugin(vue: typeof Vue): void {
@ -113,7 +103,22 @@ Vue.use(TelemetryPlugin);
Vue.use(PiniaVuePlugin); Vue.use(PiniaVuePlugin);
describe('ExecutionsList.vue', () => { describe('ExecutionsList.vue', () => {
const workflowsStore: ReturnType<typeof useWorkflowsStore> = useWorkflowsStore();
beforeEach(() => {
vi.spyOn(workflowsStore, 'fetchAllWorkflows').mockResolvedValue(workflowsData);
vi.spyOn(workflowsStore, 'getCurrentExecutions').mockResolvedValue([]);
});
afterEach(() => {
vi.clearAllMocks();
});
it('should render empty list', async () => { it('should render empty list', async () => {
vi.spyOn(workflowsStore, 'getPastExecutions').mockResolvedValueOnce({
count: 0,
results: [],
estimated: false,
});
const { queryAllByTestId, queryByTestId, getByTestId } = await renderComponent(); const { queryAllByTestId, queryByTestId, getByTestId } = await renderComponent();
await userEvent.click(getByTestId('execution-auto-refresh-checkbox')); await userEvent.click(getByTestId('execution-auto-refresh-checkbox'));
@ -124,8 +129,8 @@ describe('ExecutionsList.vue', () => {
}); });
it('should handle selection flow when loading more items', async () => { it('should handle selection flow when loading more items', async () => {
getPastExecutionsSpy = vi const storeSpy = vi
.fn() .spyOn(workflowsStore, 'getPastExecutions')
.mockResolvedValueOnce(executionsData[0]) .mockResolvedValueOnce(executionsData[0])
.mockResolvedValueOnce(executionsData[1]); .mockResolvedValueOnce(executionsData[1]);
@ -134,7 +139,7 @@ describe('ExecutionsList.vue', () => {
await userEvent.click(getByTestId('select-visible-executions-checkbox')); await userEvent.click(getByTestId('select-visible-executions-checkbox'));
expect(getPastExecutionsSpy).toHaveBeenCalledTimes(1); expect(storeSpy).toHaveBeenCalledTimes(1);
expect( expect(
getAllByTestId('select-execution-checkbox').filter((el) => getAllByTestId('select-execution-checkbox').filter((el) =>
el.contains(el.querySelector(':checked')), el.contains(el.querySelector(':checked')),
@ -145,7 +150,7 @@ describe('ExecutionsList.vue', () => {
await userEvent.click(getByTestId('load-more-button')); await userEvent.click(getByTestId('load-more-button'));
expect(getPastExecutionsSpy).toHaveBeenCalledTimes(2); expect(storeSpy).toHaveBeenCalledTimes(2);
expect(getAllByTestId('select-execution-checkbox').length).toBe(20); expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
expect( expect(
getAllByTestId('select-execution-checkbox').filter((el) => getAllByTestId('select-execution-checkbox').filter((el) =>

View file

@ -32,11 +32,8 @@ import {
IUser, IUser,
} from '@/Interface'; } from '@/Interface';
import { restApi } from '@/mixins/restApi';
import { get } from 'lodash-es'; import { get } from 'lodash-es';
import mixins from 'vue-typed-mixins';
import { isObjectLiteral } from '@/utils'; import { isObjectLiteral } from '@/utils';
import { getCredentialPermissions } from '@/permissions'; import { getCredentialPermissions } from '@/permissions';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
@ -45,8 +42,9 @@ import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeTypesStore } from '@/stores/nodeTypes'; import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useCredentialsStore } from '@/stores/credentials'; import { useCredentialsStore } from '@/stores/credentials';
import Vue from 'vue';
export const nodeHelpers = mixins(restApi).extend({ export const nodeHelpers = Vue.extend({
computed: { computed: {
...mapStores( ...mapStores(
useCredentialsStore, useCredentialsStore,

View file

@ -44,7 +44,6 @@ import {
} from '../Interface'; } from '../Interface';
import { externalHooks } from '@/mixins/externalHooks'; import { externalHooks } from '@/mixins/externalHooks';
import { restApi } from '@/mixins/restApi';
import { nodeHelpers } from '@/mixins/nodeHelpers'; import { nodeHelpers } from '@/mixins/nodeHelpers';
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
@ -325,7 +324,7 @@ function executeData(
return executeData; return executeData;
} }
export const workflowHelpers = mixins(externalHooks, nodeHelpers, restApi, showMessage).extend({ export const workflowHelpers = mixins(externalHooks, nodeHelpers, showMessage).extend({
computed: { computed: {
...mapStores( ...mapStores(
useNodeTypesStore, useNodeTypesStore,
@ -664,7 +663,7 @@ export const workflowHelpers = mixins(externalHooks, nodeHelpers, restApi, showM
if (isCurrentWorkflow) { if (isCurrentWorkflow) {
data = await this.getWorkflowDataToSave(); data = await this.getWorkflowDataToSave();
} else { } else {
const { versionId } = await this.restApi().getWorkflow(workflowId); const { versionId } = await this.workflowsStore.fetchWorkflow(workflowId);
data.versionId = versionId; data.versionId = versionId;
} }
@ -672,7 +671,7 @@ export const workflowHelpers = mixins(externalHooks, nodeHelpers, restApi, showM
data.active = active; data.active = active;
} }
const workflow = await this.restApi().updateWorkflow(workflowId, data); const workflow = await this.workflowsStore.updateWorkflow(workflowId, data);
this.workflowsStore.setWorkflowVersionId(workflow.versionId); this.workflowsStore.setWorkflowVersionId(workflow.versionId);
if (isCurrentWorkflow) { if (isCurrentWorkflow) {
@ -714,7 +713,7 @@ export const workflowHelpers = mixins(externalHooks, nodeHelpers, restApi, showM
workflowDataRequest.versionId = this.workflowsStore.workflowVersionId; workflowDataRequest.versionId = this.workflowsStore.workflowVersionId;
const workflowData = await this.restApi().updateWorkflow( const workflowData = await this.workflowsStore.updateWorkflow(
currentWorkflow, currentWorkflow,
workflowDataRequest, workflowDataRequest,
forceSave, forceSave,
@ -831,7 +830,7 @@ export const workflowHelpers = mixins(externalHooks, nodeHelpers, restApi, showM
if (tags) { if (tags) {
workflowDataRequest.tags = tags; workflowDataRequest.tags = tags;
} }
const workflowData = await this.restApi().createNewWorkflow(workflowDataRequest); const workflowData = await this.workflowsStore.createNewWorkflow(workflowDataRequest);
this.workflowsStore.addWorkflow(workflowData); this.workflowsStore.addWorkflow(workflowData);
@ -944,7 +943,7 @@ export const workflowHelpers = mixins(externalHooks, nodeHelpers, restApi, showM
async dataHasChanged(id: string) { async dataHasChanged(id: string) {
const currentData = await this.getWorkflowDataToSave(); const currentData = await this.getWorkflowDataToSave();
const data: IWorkflowDb = await this.restApi().getWorkflow(id); const data: IWorkflowDb = await this.workflowsStore.fetchWorkflow(id);
if (data !== undefined) { if (data !== undefined) {
const x = { const x = {

View file

@ -9,7 +9,6 @@ import {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { externalHooks } from '@/mixins/externalHooks'; import { externalHooks } from '@/mixins/externalHooks';
import { restApi } from '@/mixins/restApi';
import { workflowHelpers } from '@/mixins/workflowHelpers'; import { workflowHelpers } from '@/mixins/workflowHelpers';
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
@ -20,7 +19,7 @@ import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows'; import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore'; import { useRootStore } from '@/stores/n8nRootStore';
export const workflowRun = mixins(externalHooks, restApi, workflowHelpers, showMessage).extend({ export const workflowRun = mixins(externalHooks, workflowHelpers, showMessage).extend({
setup() { setup() {
return { return {
...useTitleChange(), ...useTitleChange(),
@ -45,7 +44,7 @@ export const workflowRun = mixins(externalHooks, restApi, workflowHelpers, showM
let response: IExecutionPushResponse; let response: IExecutionPushResponse;
try { try {
response = await this.restApi().runWorkflow(runData); response = await this.workflowsStore.runWorkflow(runData);
} catch (error) { } catch (error) {
this.uiStore.removeActiveAction('workflowRunning'); this.uiStore.removeActiveAction('workflowRunning');
throw error; throw error;

View file

@ -12,7 +12,7 @@ import {
updateCredential, updateCredential,
} from '@/api/credentials'; } from '@/api/credentials';
import { setCredentialSharedWith } from '@/api/credentials.ee'; import { setCredentialSharedWith } from '@/api/credentials.ee';
import { getAppNameFromCredType } from '@/utils'; import { getAppNameFromCredType, makeRestApiRequest } from '@/utils';
import { EnterpriseEditionFeature, STORES } from '@/constants'; import { EnterpriseEditionFeature, STORES } from '@/constants';
import { import {
ICredentialMap, ICredentialMap,
@ -376,5 +376,15 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
), ),
); );
}, },
async getCredentialTranslation(credentialType: string): Promise<object> {
const rootStore = useRootStore();
return await makeRestApiRequest(
rootStore.getRestApiContext,
'GET',
'/credential-translation',
{ credentialType },
);
},
}, },
}); });

View file

@ -35,6 +35,7 @@ import { useRootStore } from './n8nRootStore';
import { useUIStore } from './ui'; import { useUIStore } from './ui';
import { useUsersStore } from './users'; import { useUsersStore } from './users';
import { useVersionsStore } from './versions'; import { useVersionsStore } from './versions';
import { makeRestApiRequest } from '@/utils';
export const useSettingsStore = defineStore(STORES.SETTINGS, { export const useSettingsStore = defineStore(STORES.SETTINGS, {
state: (): ISettingsState => ({ state: (): ISettingsState => ({
@ -340,5 +341,9 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
setSaveManualExecutions(saveManualExecutions: boolean) { setSaveManualExecutions(saveManualExecutions: boolean) {
Vue.set(this, 'saveManualExecutions', saveManualExecutions); Vue.set(this, 'saveManualExecutions', saveManualExecutions);
}, },
async getTimezones(): Promise<IDataObject> {
const rootStore = useRootStore();
return makeRestApiRequest(rootStore.getRestApiContext, 'GET', '/options/timezones');
},
}, },
}); });

View file

@ -10,16 +10,23 @@ import {
} from '@/constants'; } from '@/constants';
import { import {
ExecutionsQueryFilter, ExecutionsQueryFilter,
IActivationError,
IExecutionDeleteFilter,
IExecutionPushResponse,
IExecutionResponse, IExecutionResponse,
IExecutionsCurrentSummaryExtended, IExecutionsCurrentSummaryExtended,
IExecutionsListResponse,
IExecutionsStopData,
INewWorkflowData, INewWorkflowData,
INodeUi, INodeUi,
INodeUpdatePropertiesInformation, INodeUpdatePropertiesInformation,
IPushDataExecutionFinished, IPushDataExecutionFinished,
IPushDataNodeExecuteAfter, IPushDataNodeExecuteAfter,
IPushDataUnsavedExecutionFinished, IPushDataUnsavedExecutionFinished,
IStartRunData,
IUpdateInformation, IUpdateInformation,
IUsedCredential, IUsedCredential,
IWorkflowDataUpdate,
IWorkflowDb, IWorkflowDb,
IWorkflowsMap, IWorkflowsMap,
WorkflowsState, WorkflowsState,
@ -27,6 +34,7 @@ import {
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { import {
deepCopy, deepCopy,
IAbstractEventMessage,
IConnection, IConnection,
IConnections, IConnections,
IDataObject, IDataObject,
@ -69,6 +77,8 @@ import {
stringSizeInBytes, stringSizeInBytes,
isObjectLiteral, isObjectLiteral,
isEmpty, isEmpty,
makeRestApiRequest,
unflattenExecutionData,
} from '@/utils'; } from '@/utils';
import { useNDVStore } from './ndv'; import { useNDVStore } from './ndv';
import { useNodeTypesStore } from './nodeTypes'; import { useNodeTypesStore } from './nodeTypes';
@ -345,6 +355,19 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
return this.getWorkflow(nodes, connections, copyData); return this.getWorkflow(nodes, connections, copyData);
}, },
// Returns a workflow from a given URL
async getWorkflowFromUrl(url: string): Promise<IWorkflowDb> {
const rootStore = useRootStore();
return await makeRestApiRequest(rootStore.getRestApiContext, 'GET', '/workflows/from-url', {
url,
});
},
async getActivationError(id: string): Promise<IActivationError | undefined> {
const rootStore = useRootStore();
return makeRestApiRequest(rootStore.getRestApiContext, 'GET', `/active/error/${id}`);
},
async fetchAllWorkflows(): Promise<IWorkflowDb[]> { async fetchAllWorkflows(): Promise<IWorkflowDb[]> {
const rootStore = useRootStore(); const rootStore = useRootStore();
const workflows = await getWorkflows(rootStore.getRestApiContext); const workflows = await getWorkflows(rootStore.getRestApiContext);
@ -352,7 +375,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
return workflows; return workflows;
}, },
async fetchWorkflow(id: string): Promise<IWorkflowDb> { async fetchAndSetWorkflow(id: string): Promise<IWorkflowDb> {
const rootStore = useRootStore(); const rootStore = useRootStore();
const workflow = await getWorkflow(rootStore.getRestApiContext, id); const workflow = await getWorkflow(rootStore.getRestApiContext, id);
this.addWorkflow(workflow); this.addWorkflow(workflow);
@ -1025,6 +1048,144 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
Vue.set(this, 'activeExecutions', newActiveExecutions); Vue.set(this, 'activeExecutions', newActiveExecutions);
}, },
async retryExecution(id: string, loadWorkflow?: boolean): Promise<boolean> {
let sendData;
if (loadWorkflow === true) {
sendData = {
loadWorkflow: true,
};
}
const rootStore = useRootStore();
return await makeRestApiRequest(
rootStore.getRestApiContext,
'POST',
`/executions/${id}/retry`,
sendData,
);
},
// Deletes executions
async deleteExecutions(sendData: IExecutionDeleteFilter): Promise<void> {
const rootStore = useRootStore();
return await makeRestApiRequest(
rootStore.getRestApiContext,
'POST',
'/executions/delete',
sendData as unknown as IDataObject,
);
},
// TODO: For sure needs some kind of default filter like last day, with max 10 results, ...
async getPastExecutions(
filter: IDataObject,
limit: number,
lastId?: string,
firstId?: string,
): Promise<IExecutionsListResponse> {
let sendData = {};
if (filter) {
sendData = {
filter,
firstId,
lastId,
limit,
};
}
const rootStore = useRootStore();
return makeRestApiRequest(rootStore.getRestApiContext, 'GET', '/executions', sendData);
},
async getCurrentExecutions(filter: IDataObject): Promise<IExecutionsCurrentSummaryExtended[]> {
let sendData = {};
if (filter) {
sendData = {
filter,
};
}
const rootStore = useRootStore();
return await makeRestApiRequest(
rootStore.getRestApiContext,
'GET',
'/executions-current',
sendData,
);
},
async getExecution(id: string): Promise<IExecutionResponse | undefined> {
const rootStore = useRootStore();
const response = await makeRestApiRequest(
rootStore.getRestApiContext,
'GET',
`/executions/${id}`,
);
return response && unflattenExecutionData(response);
},
async fetchWorkflow(id: string): Promise<IWorkflowDb> {
const rootStore = useRootStore();
return makeRestApiRequest(rootStore.getRestApiContext, 'GET', `/workflows/${id}`);
},
// Creates a new workflow
async createNewWorkflow(sendData: IWorkflowDataUpdate): Promise<IWorkflowDb> {
const rootStore = useRootStore();
return makeRestApiRequest(
rootStore.getRestApiContext,
'POST',
'/workflows',
sendData as unknown as IDataObject,
);
},
// Deletes a workflow
async deleteWorkflowAPI(name: string): Promise<void> {
const rootStore = useRootStore();
return makeRestApiRequest(rootStore.getRestApiContext, 'DELETE', `/workflows/${name}`);
},
// Updates an existing workflow
async updateWorkflow(
id: string,
data: IWorkflowDataUpdate,
forceSave = false,
): Promise<IWorkflowDb> {
const rootStore = useRootStore();
return makeRestApiRequest(
rootStore.getRestApiContext,
'PATCH',
`/workflows/${id}${forceSave ? '?forceSave=true' : ''}`,
data as unknown as IDataObject,
);
},
async runWorkflow(startRunData: IStartRunData): Promise<IExecutionPushResponse> {
const rootStore = useRootStore();
return await makeRestApiRequest(
rootStore.getRestApiContext,
'POST',
'/workflows/run',
startRunData as unknown as IDataObject,
);
},
async removeTestWebhook(workflowId: string): Promise<boolean> {
const rootStore = useRootStore();
return await makeRestApiRequest(
rootStore.getRestApiContext,
'DELETE',
`/test-webhook/${workflowId}`,
);
},
async stopCurrentExecution(executionId: string): Promise<IExecutionsStopData> {
const rootStore = useRootStore();
return await makeRestApiRequest(
rootStore.getRestApiContext,
'POST',
`/executions-current/${executionId}/stop`,
);
},
async loadCurrentWorkflowExecutions( async loadCurrentWorkflowExecutions(
requestFilter: ExecutionsQueryFilter, requestFilter: ExecutionsQueryFilter,
): Promise<IExecutionsSummary[]> { ): Promise<IExecutionsSummary[]> {
@ -1047,13 +1208,16 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
throw error; throw error;
} }
}, },
async fetchExecutionDataById(executionId: string): Promise<IExecutionResponse | null> { async fetchExecutionDataById(executionId: string): Promise<IExecutionResponse | null> {
const rootStore = useRootStore(); const rootStore = useRootStore();
return await getExecutionData(rootStore.getRestApiContext, executionId); return await getExecutionData(rootStore.getRestApiContext, executionId);
}, },
deleteExecution(execution: IExecutionsSummary): void { deleteExecution(execution: IExecutionsSummary): void {
this.currentWorkflowExecutions.splice(this.currentWorkflowExecutions.indexOf(execution), 1); this.currentWorkflowExecutions.splice(this.currentWorkflowExecutions.indexOf(execution), 1);
}, },
addToCurrentExecutions(executions: IExecutionsSummary[]): void { addToCurrentExecutions(executions: IExecutionsSummary[]): void {
executions.forEach((execution) => { executions.forEach((execution) => {
const exists = this.currentWorkflowExecutions.find((ex) => ex.id === execution.id); const exists = this.currentWorkflowExecutions.find((ex) => ex.id === execution.id);
@ -1062,6 +1226,23 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
} }
}); });
}, },
// Returns all the available timezones
async getExecutionEvents(id: string): Promise<IAbstractEventMessage[]> {
const rootStore = useRootStore();
return makeRestApiRequest(rootStore.getRestApiContext, 'GET', '/eventbus/execution/' + id);
},
// Binary data
async getBinaryUrl(dataPath, mode, fileName, mimeType): string {
const rootStore = useRootStore();
let restUrl = rootStore.getRestUrl;
if (restUrl.startsWith('/')) restUrl = window.location.origin + restUrl;
const url = new URL(`${restUrl}/data/${dataPath}`);
url.searchParams.append('mode', mode);
if (fileName) url.searchParams.append('fileName', fileName);
if (mimeType) url.searchParams.append('mimeType', mimeType);
return url.toString();
},
setNodePristine(nodeName: string, isPristine: boolean): void { setNodePristine(nodeName: string, isPristine: boolean): void {
Vue.set(this.nodeMetadata[nodeName], 'pristine', isPristine); Vue.set(this.nodeMetadata[nodeName], 'pristine', isPristine);
}, },

View file

@ -1,6 +1,12 @@
import axios, { AxiosRequestConfig, Method } from 'axios'; import axios, { AxiosRequestConfig, Method } from 'axios';
import { IDataObject } from 'n8n-workflow'; import { IDataObject } from 'n8n-workflow';
import type { IRestApiContext } from '@/Interface'; import type {
IExecutionFlattedResponse,
IExecutionResponse,
IRestApiContext,
IWorkflowDb,
} from '@/Interface';
import { parse } from 'flatted';
export const NO_NETWORK_ERROR_CODE = 999; export const NO_NETWORK_ERROR_CODE = 999;
@ -127,3 +133,27 @@ export async function post(
) { ) {
return await request({ method: 'POST', baseURL, endpoint, headers, data: params }); return await request({ method: 'POST', baseURL, endpoint, headers, data: params });
} }
/**
* Unflattens the Execution data.
*
* @param {IExecutionFlattedResponse} fullExecutionData The data to unflatten
*/
export function unflattenExecutionData(
fullExecutionData: IExecutionFlattedResponse,
): IExecutionResponse {
// Unflatten the data
const returnData: IExecutionResponse = {
...fullExecutionData,
workflowData: fullExecutionData.workflowData as IWorkflowDb,
data: parse(fullExecutionData.data),
};
returnData.finished = returnData.finished ? returnData.finished : false;
if (fullExecutionData.id) {
returnData.id = fullExecutionData.id;
}
return returnData;
}

View file

@ -205,7 +205,6 @@ import { copyPaste } from '@/mixins/copyPaste';
import { externalHooks } from '@/mixins/externalHooks'; import { externalHooks } from '@/mixins/externalHooks';
import { genericHelpers } from '@/mixins/genericHelpers'; import { genericHelpers } from '@/mixins/genericHelpers';
import { moveNodeWorkflow } from '@/mixins/moveNodeWorkflow'; import { moveNodeWorkflow } from '@/mixins/moveNodeWorkflow';
import { restApi } from '@/mixins/restApi';
import useGlobalLinkActions from '@/composables/useGlobalLinkActions'; import useGlobalLinkActions from '@/composables/useGlobalLinkActions';
import useCanvasMouseSelect from '@/composables/useCanvasMouseSelect'; import useCanvasMouseSelect from '@/composables/useCanvasMouseSelect';
import { showMessage } from '@/mixins/showMessage'; import { showMessage } from '@/mixins/showMessage';
@ -322,7 +321,6 @@ export default mixins(
externalHooks, externalHooks,
genericHelpers, genericHelpers,
moveNodeWorkflow, moveNodeWorkflow,
restApi,
showMessage, showMessage,
workflowHelpers, workflowHelpers,
workflowRun, workflowRun,
@ -783,7 +781,7 @@ export default mixins(
this.resetWorkspace(); this.resetWorkspace();
let data: IExecutionResponse | undefined; let data: IExecutionResponse | undefined;
try { try {
data = await this.restApi().getExecution(executionId); data = await this.workflowsStore.getExecution(executionId);
} catch (error) { } catch (error) {
this.$showError(error, this.$locale.baseText('nodeView.showError.openExecution.title')); this.$showError(error, this.$locale.baseText('nodeView.showError.openExecution.title'));
return; return;
@ -1403,14 +1401,14 @@ export default mixins(
try { try {
this.stopExecutionInProgress = true; this.stopExecutionInProgress = true;
await this.restApi().stopCurrentExecution(executionId); await this.workflowsStore.stopCurrentExecution(executionId);
this.$showMessage({ this.$showMessage({
title: this.$locale.baseText('nodeView.showMessage.stopExecutionTry.title'), title: this.$locale.baseText('nodeView.showMessage.stopExecutionTry.title'),
type: 'success', type: 'success',
}); });
} catch (error) { } catch (error) {
// Execution stop might fail when the execution has already finished. Let's treat this here. // Execution stop might fail when the execution has already finished. Let's treat this here.
const execution = await this.restApi().getExecution(executionId); const execution = await this.workflowsStore.getExecution(executionId);
if (execution === undefined) { if (execution === undefined) {
// execution finished but was not saved (e.g. due to low connectivity) // execution finished but was not saved (e.g. due to low connectivity)
@ -1476,7 +1474,7 @@ export default mixins(
async stopWaitingForWebhook() { async stopWaitingForWebhook() {
try { try {
await this.restApi().removeTestWebhook(this.workflowsStore.workflowId); await this.workflowsStore.removeTestWebhook(this.workflowsStore.workflowId);
} catch (error) { } catch (error) {
this.$showError( this.$showError(
error, error,
@ -1549,7 +1547,7 @@ export default mixins(
this.startLoading(); this.startLoading();
try { try {
workflowData = await this.restApi().getWorkflowFromUrl(url); workflowData = await this.workflowsStore.getWorkflowFromUrl(url);
} catch (error) { } catch (error) {
this.stopLoading(); this.stopLoading();
this.$showError( this.$showError(
@ -2586,7 +2584,7 @@ export default mixins(
if (workflowId !== null) { if (workflowId !== null) {
let workflow: IWorkflowDb | undefined = undefined; let workflow: IWorkflowDb | undefined = undefined;
try { try {
workflow = await this.restApi().getWorkflow(workflowId); workflow = await this.workflowsStore.fetchWorkflow(workflowId);
} catch (error) { } catch (error) {
this.$showError(error, this.$locale.baseText('openWorkflow.workflowNotFoundError')); this.$showError(error, this.$locale.baseText('openWorkflow.workflowNotFoundError'));
@ -3586,8 +3584,7 @@ export default mixins(
return Promise.resolve(); return Promise.resolve();
}, },
async loadActiveWorkflows(): Promise<void> { async loadActiveWorkflows(): Promise<void> {
const activeWorkflows = await this.restApi().getActiveWorkflows(); await this.workflowsStore.fetchActiveWorkflows();
this.workflowsStore.activeWorkflows = activeWorkflows;
}, },
async loadNodeTypes(): Promise<void> { async loadNodeTypes(): Promise<void> {
await this.nodeTypesStore.getNodeTypes(); await this.nodeTypesStore.getNodeTypes();

View file

@ -14,16 +14,14 @@ import { showMessage } from '@/mixins/showMessage';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { IFormBoxConfig } from '@/Interface'; import { IFormBoxConfig } from '@/Interface';
import { VIEWS, ASSUMPTION_EXPERIMENT } from '@/constants'; import { VIEWS } from '@/constants';
import { restApi } from '@/mixins/restApi';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui'; import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users'; import { useUsersStore } from '@/stores/users';
import { useCredentialsStore } from '@/stores/credentials'; import { useCredentialsStore } from '@/stores/credentials';
import { usePostHog } from '@/stores/posthog';
export default mixins(showMessage, restApi).extend({ export default mixins(showMessage).extend({
name: 'SetupView', name: 'SetupView',
components: { components: {
AuthView, AuthView,