perf(editor): Fix WorkflowDetails excessive re-rendering (#10767)

This commit is contained in:
Raúl Gómez Morales 2024-09-11 11:46:18 +02:00 committed by GitHub
parent 21936c88a8
commit 00013a2069
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 48 deletions

View file

@ -164,7 +164,16 @@ async function navigateToExecutionsView(openInNewTab: boolean) {
<div> <div>
<div :class="{ 'main-header': true, expanded: !uiStore.sidebarMenuCollapsed }"> <div :class="{ 'main-header': true, expanded: !uiStore.sidebarMenuCollapsed }">
<div v-show="!hideMenuBar" class="top-menu"> <div v-show="!hideMenuBar" class="top-menu">
<WorkflowDetails v-if="workflow?.name" :workflow="workflow" :read-only="readOnly" /> <WorkflowDetails
v-if="workflow?.name"
:id="workflow.id"
:tags="workflow.tags"
:name="workflow.name"
:meta="workflow.meta"
:scopes="workflow.scopes"
:active="workflow.active"
:read-only="readOnly"
/>
<TabBar <TabBar
v-if="onWorkflowPage" v-if="onWorkflowPage"
:items="tabBarItems" :items="tabBarItems"

View file

@ -59,7 +59,7 @@ describe('WorkflowDetails', () => {
it('renders workflow name and tags', async () => { it('renders workflow name and tags', async () => {
const { getByTestId, getByText } = renderComponent({ const { getByTestId, getByText } = renderComponent({
props: { props: {
workflow, ...workflow,
readOnly: false, readOnly: false,
}, },
}); });
@ -76,7 +76,7 @@ describe('WorkflowDetails', () => {
const onSaveButtonClick = vi.fn(); const onSaveButtonClick = vi.fn();
const { getByTestId } = renderComponent({ const { getByTestId } = renderComponent({
props: { props: {
workflow, ...workflow,
readOnly: false, readOnly: false,
}, },
global: { global: {
@ -95,7 +95,7 @@ describe('WorkflowDetails', () => {
const { getByTestId } = renderComponent({ const { getByTestId } = renderComponent({
props: { props: {
workflow, ...workflow,
readOnly: false, readOnly: false,
}, },
}); });

View file

@ -58,8 +58,13 @@ import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
import { useLocalStorage } from '@vueuse/core'; import { useLocalStorage } from '@vueuse/core';
const props = defineProps<{ const props = defineProps<{
workflow: IWorkflowDb;
readOnly?: boolean; readOnly?: boolean;
id: IWorkflowDb['id'];
tags: IWorkflowDb['tags'];
name: IWorkflowDb['name'];
meta: IWorkflowDb['meta'];
scopes: IWorkflowDb['scopes'];
active: IWorkflowDb['active'];
}>(); }>();
const $style = useCssModule(); const $style = useCssModule();
@ -115,11 +120,7 @@ const hasChanged = (prev: string[], curr: string[]) => {
}; };
const isNewWorkflow = computed(() => { const isNewWorkflow = computed(() => {
return ( return !props.id || props.id === PLACEHOLDER_EMPTY_WORKFLOW_ID || props.id === 'new';
!props.workflow.id ||
props.workflow.id === PLACEHOLDER_EMPTY_WORKFLOW_ID ||
props.workflow.id === 'new'
);
}); });
const isWorkflowSaving = computed(() => { const isWorkflowSaving = computed(() => {
@ -138,9 +139,7 @@ const onExecutionsTab = computed(() => {
].includes((route.name as string) || ''); ].includes((route.name as string) || '');
}); });
const workflowPermissions = computed( const workflowPermissions = computed(() => getResourcePermissions(props.scopes).workflow);
() => getResourcePermissions(workflowsStore.getWorkflowById(props.workflow.id)?.scopes).workflow,
);
const workflowMenuItems = computed<ActionDropdownItem[]>(() => { const workflowMenuItems = computed<ActionDropdownItem[]>(() => {
const actions: ActionDropdownItem[] = [ const actions: ActionDropdownItem[] = [
@ -155,7 +154,7 @@ const workflowMenuItems = computed<ActionDropdownItem[]>(() => {
actions.unshift({ actions.unshift({
id: WORKFLOW_MENU_ACTIONS.DUPLICATE, id: WORKFLOW_MENU_ACTIONS.DUPLICATE,
label: locale.baseText('menuActions.duplicate'), label: locale.baseText('menuActions.duplicate'),
disabled: !onWorkflowPage.value || !props.workflow.id, disabled: !onWorkflowPage.value || !props.id,
}); });
actions.push( actions.push(
@ -219,11 +218,11 @@ const isWorkflowHistoryFeatureEnabled = computed(() => {
}); });
const workflowTagIds = computed(() => { const workflowTagIds = computed(() => {
return (props.workflow.tags ?? []).map((tag) => (typeof tag === 'string' ? tag : tag.id)); return (props.tags ?? []).map((tag) => (typeof tag === 'string' ? tag : tag.id));
}); });
watch( watch(
() => props.workflow.id, () => props.id,
() => { () => {
isTagsEditEnabled.value = false; isTagsEditEnabled.value = false;
isNameEditEnabled.value = false; isNameEditEnabled.value = false;
@ -232,8 +231,8 @@ watch(
function getWorkflowId(): string | undefined { function getWorkflowId(): string | undefined {
let id: string | undefined = undefined; let id: string | undefined = undefined;
if (props.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID) { if (props.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID) {
id = props.workflow.id; id = props.id;
} else if (route.params.name && route.params.name !== 'new') { } else if (route.params.name && route.params.name !== 'new') {
id = route.params.name as string; id = route.params.name as string;
} }
@ -249,8 +248,8 @@ async function onSaveButtonClick() {
const id = getWorkflowId(); const id = getWorkflowId();
const name = props.workflow.name; const name = props.name;
const tags = props.workflow.tags as string[]; const tags = props.tags as string[];
const saved = await workflowHelpers.saveCurrentWorkflow({ const saved = await workflowHelpers.saveCurrentWorkflow({
id, id,
@ -266,7 +265,7 @@ async function onSaveButtonClick() {
if (route.name === VIEWS.EXECUTION_DEBUG) { if (route.name === VIEWS.EXECUTION_DEBUG) {
await router.replace({ await router.replace({
name: VIEWS.WORKFLOW, name: VIEWS.WORKFLOW,
params: { name: props.workflow.id }, params: { name: props.id },
}); });
} }
} }
@ -275,18 +274,18 @@ async function onSaveButtonClick() {
function onShareButtonClick() { function onShareButtonClick() {
uiStore.openModalWithData({ uiStore.openModalWithData({
name: WORKFLOW_SHARE_MODAL_KEY, name: WORKFLOW_SHARE_MODAL_KEY,
data: { id: props.workflow.id }, data: { id: props.id },
}); });
telemetry.track('User opened sharing modal', { telemetry.track('User opened sharing modal', {
workflow_id: props.workflow.id, workflow_id: props.id,
user_id_sharer: usersStore.currentUser?.id, user_id_sharer: usersStore.currentUser?.id,
sub_view: route.name === VIEWS.WORKFLOWS ? 'Workflows listing' : 'Workflow editor', sub_view: route.name === VIEWS.WORKFLOWS ? 'Workflows listing' : 'Workflow editor',
}); });
} }
function onTagsEditEnable() { function onTagsEditEnable() {
appliedTagIds.value = (props.workflow.tags ?? []) as string[]; appliedTagIds.value = (props.tags ?? []) as string[];
isTagsEditEnabled.value = true; isTagsEditEnabled.value = true;
setTimeout(() => { setTimeout(() => {
@ -297,7 +296,7 @@ function onTagsEditEnable() {
} }
async function onTagsBlur() { async function onTagsBlur() {
const current = (props.workflow.tags ?? []) as string[]; const current = (props.tags ?? []) as string[];
const tags = appliedTagIds.value; const tags = appliedTagIds.value;
if (!hasChanged(current, tags)) { if (!hasChanged(current, tags)) {
isTagsEditEnabled.value = false; isTagsEditEnabled.value = false;
@ -311,7 +310,7 @@ async function onTagsBlur() {
const saved = await workflowHelpers.saveCurrentWorkflow({ tags }); const saved = await workflowHelpers.saveCurrentWorkflow({ tags });
telemetry.track('User edited workflow tags', { telemetry.track('User edited workflow tags', {
workflow_id: props.workflow.id, workflow_id: props.id,
new_tag_count: tags.length, new_tag_count: tags.length,
}); });
@ -355,7 +354,7 @@ async function onNameSubmit({
return; return;
} }
if (newName === props.workflow.name) { if (newName === props.name) {
isNameEditEnabled.value = false; isNameEditEnabled.value = false;
onSubmit(true); onSubmit(true);
@ -405,9 +404,9 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise<void
uiStore.openModalWithData({ uiStore.openModalWithData({
name: DUPLICATE_MODAL_KEY, name: DUPLICATE_MODAL_KEY,
data: { data: {
id: props.workflow.id, id: props.id,
name: props.workflow.name, name: props.name,
tags: props.workflow.tags, tags: props.tags,
}, },
}); });
break; break;
@ -418,7 +417,7 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise<void
const exportData: IWorkflowToShare = { const exportData: IWorkflowToShare = {
...data, ...data,
meta: { meta: {
...props.workflow.meta, ...props.meta,
instanceId: rootStore.instanceId, instanceId: rootStore.instanceId,
}, },
tags: (tags ?? []).map((tagId) => { tags: (tags ?? []).map((tagId) => {
@ -432,7 +431,7 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise<void
type: 'application/json;charset=utf-8', type: 'application/json;charset=utf-8',
}); });
let name = props.workflow.name || 'unsaved_workflow'; let name = props.name || 'unsaved_workflow';
name = name.replace(/[^a-z0-9]/gi, '_'); name = name.replace(/[^a-z0-9]/gi, '_');
telemetry.track('User exported workflow', { workflow_id: workflowData.id }); telemetry.track('User exported workflow', { workflow_id: workflowData.id });
@ -533,7 +532,7 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise<void
case WORKFLOW_MENU_ACTIONS.DELETE: { case WORKFLOW_MENU_ACTIONS.DELETE: {
const deleteConfirmed = await message.confirm( const deleteConfirmed = await message.confirm(
locale.baseText('mainSidebar.confirmMessage.workflowDelete.message', { locale.baseText('mainSidebar.confirmMessage.workflowDelete.message', {
interpolate: { workflowName: props.workflow.name }, interpolate: { workflowName: props.name },
}), }),
locale.baseText('mainSidebar.confirmMessage.workflowDelete.headline'), locale.baseText('mainSidebar.confirmMessage.workflowDelete.headline'),
{ {
@ -552,7 +551,7 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise<void
} }
try { try {
await workflowsStore.deleteWorkflow(props.workflow.id); await workflowsStore.deleteWorkflow(props.id);
} catch (error) { } catch (error) {
toast.showError(error, locale.baseText('generic.deleteWorkflowError')); toast.showError(error, locale.baseText('generic.deleteWorkflowError'));
return; return;
@ -608,15 +607,10 @@ function showCreateWorkflowSuccessToast(id?: string) {
<div :class="$style.container"> <div :class="$style.container">
<BreakpointsObserver :value-x-s="15" :value-s-m="25" :value-m-d="50" class="name-container"> <BreakpointsObserver :value-x-s="15" :value-s-m="25" :value-m-d="50" class="name-container">
<template #default="{ value }"> <template #default="{ value }">
<ShortenName <ShortenName :name="name" :limit="value" :custom="true" test-id="workflow-name-input">
:name="workflow.name"
:limit="value"
:custom="true"
test-id="workflow-name-input"
>
<template #default="{ shortenedName }"> <template #default="{ shortenedName }">
<InlineTextEdit <InlineTextEdit
:model-value="workflow.name" :model-value="name"
:preview-value="shortenedName" :preview-value="shortenedName"
:is-edit-enabled="isNameEditEnabled" :is-edit-enabled="isNameEditEnabled"
:max-length="MAX_WORKFLOW_NAME_LENGTH" :max-length="MAX_WORKFLOW_NAME_LENGTH"
@ -645,9 +639,7 @@ function showCreateWorkflowSuccessToast(id?: string) {
/> />
<div <div
v-else-if=" v-else-if="
(workflow.tags ?? []).length === 0 && (tags ?? []).length === 0 && !readOnly && (isNewWorkflow || workflowPermissions.update)
!readOnly &&
(isNewWorkflow || workflowPermissions.update)
" "
> >
<span class="add-tag clickable" data-test-id="new-tag-link" @click="onTagsEditEnable"> <span class="add-tag clickable" data-test-id="new-tag-link" @click="onTagsEditEnable">
@ -656,7 +648,7 @@ function showCreateWorkflowSuccessToast(id?: string) {
</div> </div>
<WorkflowTagsContainer <WorkflowTagsContainer
v-else v-else
:key="workflow.id" :key="id"
:tag-ids="workflowTagIds" :tag-ids="workflowTagIds"
:clickable="true" :clickable="true"
:responsive="true" :responsive="true"
@ -669,8 +661,8 @@ function showCreateWorkflowSuccessToast(id?: string) {
<PushConnectionTracker class="actions"> <PushConnectionTracker class="actions">
<span :class="`activator ${$style.group}`"> <span :class="`activator ${$style.group}`">
<WorkflowActivator <WorkflowActivator
:workflow-active="workflow.active" :workflow-active="active"
:workflow-id="workflow.id" :workflow-id="id"
:workflow-permissions="workflowPermissions" :workflow-permissions="workflowPermissions"
/> />
</span> </span>
@ -727,7 +719,7 @@ function showCreateWorkflowSuccessToast(id?: string) {
@click="onSaveButtonClick" @click="onSaveButtonClick"
/> />
<WorkflowHistoryButton <WorkflowHistoryButton
:workflow-id="props.workflow.id" :workflow-id="props.id"
:is-feature-enabled="isWorkflowHistoryFeatureEnabled" :is-feature-enabled="isWorkflowHistoryFeatureEnabled"
:is-new-workflow="isNewWorkflow" :is-new-workflow="isNewWorkflow"
@upgrade="goToWorkflowHistoryUpgrade" @upgrade="goToWorkflowHistoryUpgrade"