feat(editor): Workflow history [WIP]- Add restore and clone into new workflow actions (no-changelog) (#7359)

This commit is contained in:
Csaba Tuncsik
2023-10-09 13:50:08 +02:00
committed by GitHub
parent 77643e5ccb
commit b3247e5935
11 changed files with 321 additions and 46 deletions

View File

@@ -139,6 +139,16 @@
<DebugPaywallModal data-test-id="debug-paywall-modal" :modalName="modalName" :data="data" />
</template>
</ModalRoot>
<ModalRoot :name="WORKFLOW_HISTORY_VERSION_RESTORE">
<template #default="{ modalName, data }">
<WorkflowHistoryVersionRestoreModal
data-test-id="workflow-history-version-restore-modal"
:modalName="modalName"
:data="data"
/>
</template>
</ModalRoot>
</div>
</template>
@@ -172,6 +182,7 @@ import {
EXTERNAL_SECRETS_PROVIDER_MODAL_KEY,
DEBUG_PAYWALL_MODAL_KEY,
MFA_SETUP_MODAL_KEY,
WORKFLOW_HISTORY_VERSION_RESTORE,
} from '@/constants';
import AboutModal from './AboutModal.vue';
@@ -202,6 +213,7 @@ import SourceControlPushModal from '@/components/SourceControlPushModal.ee.vue';
import SourceControlPullModal from '@/components/SourceControlPullModal.ee.vue';
import ExternalSecretsProviderModal from '@/components/ExternalSecretsProviderModal.ee.vue';
import DebugPaywallModal from '@/components/DebugPaywallModal.vue';
import WorkflowHistoryVersionRestoreModal from '@/components/WorkflowHistory/WorkflowHistoryVersionRestoreModal.vue';
export default defineComponent({
name: 'Modals',
@@ -234,6 +246,7 @@ export default defineComponent({
ExternalSecretsProviderModal,
DebugPaywallModal,
MfaSetupModal,
WorkflowHistoryVersionRestoreModal,
},
data: () => ({
CHAT_EMBED_MODAL_KEY,
@@ -263,6 +276,7 @@ export default defineComponent({
EXTERNAL_SECRETS_PROVIDER_MODAL_KEY,
DEBUG_PAYWALL_MODAL_KEY,
MFA_SETUP_MODAL_KEY,
WORKFLOW_HISTORY_VERSION_RESTORE,
}),
});
</script>

View File

@@ -24,7 +24,11 @@ const props = defineProps<{
const emit = defineEmits<{
(
event: 'action',
value: { action: WorkflowHistoryActionTypes[number]; id: WorkflowVersionId },
value: {
action: WorkflowHistoryActionTypes[number];
id: WorkflowVersionId;
data: { formattedCreatedAt: string };
},
): void;
(event: 'preview', value: { event: MouseEvent; id: WorkflowVersionId }): void;
(event: 'loadMore', value: WorkflowHistoryRequestParams): void;
@@ -67,12 +71,14 @@ const observeElement = (element: Element) => {
const onAction = ({
action,
id,
data,
}: {
action: WorkflowHistoryActionTypes[number];
id: WorkflowVersionId;
data: { formattedCreatedAt: string };
}) => {
shouldAutoScroll.value = false;
emit('action', { action, id });
emit('action', { action, id, data });
};
const onPreview = ({ event, id }: { event: MouseEvent; id: WorkflowVersionId }) => {

View File

@@ -18,7 +18,11 @@ const props = defineProps<{
const emit = defineEmits<{
(
event: 'action',
value: { action: WorkflowHistoryActionTypes[number]; id: WorkflowVersionId },
value: {
action: WorkflowHistoryActionTypes[number];
id: WorkflowVersionId;
data: { formattedCreatedAt: string };
},
): void;
(event: 'preview', value: { event: MouseEvent; id: WorkflowVersionId }): void;
(event: 'mounted', value: { index: number; offsetTop: number; isActive: boolean }): void;
@@ -31,11 +35,11 @@ const itemElement = ref<HTMLElement | null>(null);
const authorElement = ref<HTMLElement | null>(null);
const isAuthorElementTruncated = ref(false);
const formattedCreatedAtDate = computed<string>(() => {
const formattedCreatedAt = computed<string>(() => {
const currentYear = new Date().getFullYear().toString();
const [date, time] = dateformat(
props.item.createdAt,
`${props.item.createdAt.startsWith(currentYear) ? '' : 'yyyy '} mmm d"#"HH:MM`,
`${props.item.createdAt.startsWith(currentYear) ? '' : 'yyyy '}mmm d"#"HH:MM`,
).split('#');
return i18n.baseText('workflowHistory.item.createdAt', { interpolate: { date, time } });
@@ -60,7 +64,11 @@ const idLabel = computed<string>(() =>
);
const onAction = (action: WorkflowHistoryActionTypes[number]) => {
emit('action', { action, id: props.item.versionId });
emit('action', {
action,
id: props.item.versionId,
data: { formattedCreatedAt: formattedCreatedAt.value },
});
};
const onVisibleChange = (visible: boolean) => {
@@ -92,7 +100,7 @@ onMounted(() => {
}"
>
<p @click="onItemClick">
<time :datetime="item.createdAt">{{ formattedCreatedAtDate }}</time>
<time :datetime="item.createdAt">{{ formattedCreatedAt }}</time>
<n8n-tooltip placement="right-end" :disabled="authors.size < 2 && !isAuthorElementTruncated">
<template #content>{{ props.item.authors }}</template>
<span ref="authorElement">{{ authors.label }}</span>

View File

@@ -0,0 +1,90 @@
<script lang="ts" setup>
import { useI18n } from '@/composables';
import Modal from '@/components/Modal.vue';
import { useUIStore } from '@/stores';
const props = defineProps<{
modalName: string;
data: {
isWorkflowActivated: boolean;
formattedCreatedAt: string;
beforeClose: () => void;
buttons: Array<{
text: string;
type: string;
action: () => void;
}>;
};
}>();
const i18n = useI18n();
const uiStore = useUIStore();
const closeModal = () => {
uiStore.closeModal(props.modalName);
};
</script>
<template>
<Modal width="500px" :name="props.modalName" :before-close="props.data.beforeClose">
<template #header>
<n8n-heading tag="h2" size="xlarge">
{{ i18n.baseText('workflowHistory.action.restore.modal.title') }}
</n8n-heading>
</template>
<template #content>
<div>
<n8n-text>
<i18n-t keypath="workflowHistory.action.restore.modal.subtitle" tag="span">
<template #date>
<strong>{{ props.data.formattedCreatedAt }}</strong>
</template>
</i18n-t>
<br />
<br />
<i18n-t
v-if="props.data.isWorkflowActivated"
keypath="workflowHistory.action.restore.modal.text"
tag="span"
>
<template #buttonText>
&ldquo;{{
i18n.baseText('workflowHistory.action.restore.modal.button.deactivateAndRestore')
}}&rdquo;
</template>
</i18n-t>
</n8n-text>
</div>
</template>
<template #footer>
<div :class="$style.footer">
<n8n-button
v-for="(button, index) in props.data.buttons"
size="medium"
:key="index"
:type="button.type"
@click="
() => {
button.action();
closeModal();
}
"
>
{{ button.text }}
</n8n-button>
</div>
</template>
</Modal>
</template>
<style module lang="scss">
.footer {
display: flex;
flex-direction: row;
justify-content: flex-end;
button {
margin-left: var(--spacing-2xs);
}
}
</style>

View File

@@ -91,10 +91,10 @@ describe('WorkflowHistoryList', () => {
await userEvent.click(within(listItem).getByText(/ID: /));
expect(emitted().preview).toEqual([
[
expect.objectContaining({
{
id: items[items.length - 1].versionId,
event: expect.any(MouseEvent),
}),
},
],
]);
@@ -141,7 +141,15 @@ describe('WorkflowHistoryList', () => {
expect(actionsDropdown).toBeInTheDocument();
await userEvent.click(within(actionsDropdown).getByTestId(`action-${action}`));
expect(emitted().action).toEqual([[{ action, id: items[index].versionId }]]);
expect(emitted().action).toEqual([
[
{
action,
id: items[index].versionId,
data: { formattedCreatedAt: expect.any(String) },
},
],
]);
});
it('should show upgrade message', async () => {

View File

@@ -70,7 +70,9 @@ describe('WorkflowHistoryListItem', () => {
expect(getByTestId('action-toggle-dropdown')).toBeInTheDocument();
await userEvent.click(getByTestId(`action-${action}`));
expect(emitted().action).toEqual([[{ action, id: item.versionId }]]);
expect(emitted().action).toEqual([
[{ action, id: item.versionId, data: { formattedCreatedAt: expect.any(String) } }],
]);
expect(queryByText(/Latest saved/)).not.toBeInTheDocument();
expect(emitted().mounted).toEqual([[{ index: 2, isActive: true, offsetTop: 0 }]]);