mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(editor): Implement 'Shared with you' section in the main navigation (#15140)
This commit is contained in:
committed by
GitHub
parent
abdbe50907
commit
1c65e82b38
@@ -163,6 +163,7 @@ function moveResource() {
|
||||
:resource-type="ResourceType.Credential"
|
||||
:resource-type-label="resourceTypeLabel"
|
||||
:personal-project="projectsStore.personalProject"
|
||||
:show-badge-border="false"
|
||||
/>
|
||||
<n8n-action-toggle
|
||||
data-test-id="credential-card-actions"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { VIEWS } from '@/constants';
|
||||
import type { Project } from '@/types/projects.types';
|
||||
import { computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
type Props = {
|
||||
personalProject: Project;
|
||||
resourceType?: 'workflows' | 'credentials';
|
||||
};
|
||||
|
||||
const i18n = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
resourceType: 'workflows',
|
||||
});
|
||||
|
||||
const heading = computed(() => {
|
||||
return i18n.baseText('workflows.empty.shared-with-me', {
|
||||
interpolate: {
|
||||
resource: i18n
|
||||
.baseText(`generic.${props.resourceType === 'workflows' ? 'workflow' : 'credential'}`)
|
||||
.toLowerCase(),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const onPersonalLinkClick = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
void router.push({
|
||||
name: VIEWS.PROJECTS_WORKFLOWS,
|
||||
params: { projectId: props.personalProject.id },
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n8n-action-box
|
||||
data-test-id="empty-shared-action-box"
|
||||
:heading="heading"
|
||||
:description="i18n.baseText('workflows.empty.shared-with-me.link')"
|
||||
@description-click="onPersonalLinkClick"
|
||||
/>
|
||||
</template>
|
||||
@@ -12,7 +12,7 @@ import { VIEWS } from '@/constants';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { waitFor, within } from '@testing-library/vue';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useOverview } from '@/composables/useOverview';
|
||||
import { useProjectPages } from '@/composables/useProjectPages';
|
||||
|
||||
const mockPush = vi.fn();
|
||||
vi.mock('vue-router', async () => {
|
||||
@@ -31,9 +31,10 @@ vi.mock('vue-router', async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@/composables/useOverview', () => ({
|
||||
useOverview: vi.fn().mockReturnValue({
|
||||
vi.mock('@/composables/useProjectPages', () => ({
|
||||
useProjectPages: vi.fn().mockReturnValue({
|
||||
isOverviewSubPage: false,
|
||||
isSharedSubPage: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -52,7 +53,7 @@ const renderComponent = createComponentRenderer(ProjectHeader, {
|
||||
let route: ReturnType<typeof router.useRoute>;
|
||||
let projectsStore: ReturnType<typeof mockedStore<typeof useProjectsStore>>;
|
||||
let settingsStore: ReturnType<typeof mockedStore<typeof useSettingsStore>>;
|
||||
let overview: ReturnType<typeof useOverview>;
|
||||
let projectPages: ReturnType<typeof useProjectPages>;
|
||||
|
||||
describe('ProjectHeader', () => {
|
||||
beforeEach(() => {
|
||||
@@ -60,7 +61,7 @@ describe('ProjectHeader', () => {
|
||||
route = router.useRoute();
|
||||
projectsStore = mockedStore(useProjectsStore);
|
||||
settingsStore = mockedStore(useSettingsStore);
|
||||
overview = useOverview();
|
||||
projectPages = useProjectPages();
|
||||
|
||||
projectsStore.teamProjectsLimit = -1;
|
||||
settingsStore.settings.folders = { enabled: false };
|
||||
@@ -71,19 +72,20 @@ describe('ProjectHeader', () => {
|
||||
});
|
||||
|
||||
it('should not render title icon on overview page', async () => {
|
||||
vi.spyOn(overview, 'isOverviewSubPage', 'get').mockReturnValue(true);
|
||||
vi.spyOn(projectPages, 'isOverviewSubPage', 'get').mockReturnValue(true);
|
||||
const { container } = renderComponent();
|
||||
|
||||
expect(container.querySelector('.fa-home')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the correct icon', async () => {
|
||||
vi.spyOn(overview, 'isOverviewSubPage', 'get').mockReturnValue(false);
|
||||
vi.spyOn(projectPages, 'isOverviewSubPage', 'get').mockReturnValue(false);
|
||||
const { container, rerender } = renderComponent();
|
||||
|
||||
// We no longer render icon for personal project
|
||||
projectsStore.currentProject = { type: ProjectTypes.Personal } as Project;
|
||||
await rerender({});
|
||||
expect(container.querySelector('.fa-user')).toBeVisible();
|
||||
expect(container.querySelector('.fa-user')).not.toBeInTheDocument();
|
||||
|
||||
const projectName = 'My Project';
|
||||
projectsStore.currentProject = { name: projectName } as Project;
|
||||
@@ -91,23 +93,55 @@ describe('ProjectHeader', () => {
|
||||
expect(container.querySelector('.fa-layer-group')).toBeVisible();
|
||||
});
|
||||
|
||||
it('should render the correct title and subtitle', async () => {
|
||||
const { getByText, queryByText, rerender } = renderComponent();
|
||||
const subtitle = 'All the workflows, credentials and executions you have access to';
|
||||
it('Overview: should render the correct title and subtitle', async () => {
|
||||
vi.spyOn(projectPages, 'isOverviewSubPage', 'get').mockReturnValue(true);
|
||||
const { getByTestId, rerender } = renderComponent();
|
||||
const overviewSubtitle = 'All the workflows, credentials and executions you have access to';
|
||||
|
||||
expect(getByText('Overview')).toBeVisible();
|
||||
expect(getByText(subtitle)).toBeVisible();
|
||||
await rerender({});
|
||||
|
||||
expect(getByTestId('project-name')).toHaveTextContent('Overview');
|
||||
expect(getByTestId('project-subtitle')).toHaveTextContent(overviewSubtitle);
|
||||
});
|
||||
|
||||
it('Shared with you: should render the correct title and subtitle', async () => {
|
||||
vi.spyOn(projectPages, 'isOverviewSubPage', 'get').mockReturnValue(false);
|
||||
vi.spyOn(projectPages, 'isSharedSubPage', 'get').mockReturnValue(true);
|
||||
const { getByTestId, rerender } = renderComponent();
|
||||
const sharedSubtitle = 'Workflows and credentials other users have shared with you';
|
||||
|
||||
await rerender({});
|
||||
|
||||
expect(getByTestId('project-name')).toHaveTextContent('Shared with you');
|
||||
expect(getByTestId('project-subtitle')).toHaveTextContent(sharedSubtitle);
|
||||
});
|
||||
|
||||
it('Personal: should render the correct title and subtitle', async () => {
|
||||
vi.spyOn(projectPages, 'isOverviewSubPage', 'get').mockReturnValue(false);
|
||||
vi.spyOn(projectPages, 'isSharedSubPage', 'get').mockReturnValue(false);
|
||||
const { getByTestId, rerender } = renderComponent();
|
||||
const personalSubtitle = 'Workflows and credentials owned by you';
|
||||
|
||||
projectsStore.currentProject = { type: ProjectTypes.Personal } as Project;
|
||||
|
||||
await rerender({});
|
||||
expect(getByText('Personal')).toBeVisible();
|
||||
expect(queryByText(subtitle)).not.toBeInTheDocument();
|
||||
|
||||
expect(getByTestId('project-name')).toHaveTextContent('Personal');
|
||||
expect(getByTestId('project-subtitle')).toHaveTextContent(personalSubtitle);
|
||||
});
|
||||
|
||||
it('Team project: should render the correct title and subtitle', async () => {
|
||||
vi.spyOn(projectPages, 'isOverviewSubPage', 'get').mockReturnValue(false);
|
||||
vi.spyOn(projectPages, 'isSharedSubPage', 'get').mockReturnValue(false);
|
||||
const { getByTestId, queryByTestId, rerender } = renderComponent();
|
||||
|
||||
const projectName = 'My Project';
|
||||
projectsStore.currentProject = { name: projectName } as Project;
|
||||
|
||||
await rerender({});
|
||||
expect(getByText(projectName)).toBeVisible();
|
||||
expect(queryByText(subtitle)).not.toBeInTheDocument();
|
||||
|
||||
expect(getByTestId('project-name')).toHaveTextContent(projectName);
|
||||
expect(queryByTestId('project-subtitle')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should overwrite default subtitle with slot', () => {
|
||||
@@ -130,9 +164,9 @@ describe('ProjectHeader', () => {
|
||||
renderComponent();
|
||||
|
||||
expect(projectTabsSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
expect.objectContaining({
|
||||
'show-settings': true,
|
||||
},
|
||||
}),
|
||||
null,
|
||||
);
|
||||
});
|
||||
@@ -143,9 +177,9 @@ describe('ProjectHeader', () => {
|
||||
renderComponent();
|
||||
|
||||
expect(projectTabsSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
expect.objectContaining({
|
||||
'show-settings': false,
|
||||
},
|
||||
}),
|
||||
null,
|
||||
);
|
||||
});
|
||||
@@ -159,9 +193,9 @@ describe('ProjectHeader', () => {
|
||||
renderComponent();
|
||||
|
||||
expect(projectTabsSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
expect.objectContaining({
|
||||
'show-settings': false,
|
||||
},
|
||||
}),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { VIEWS } from '@/constants';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
import ProjectCreateResource from '@/components/Projects/ProjectCreateResource.vue';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useOverview } from '@/composables/useOverview';
|
||||
import { useProjectPages } from '@/composables/useProjectPages';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -21,7 +21,7 @@ const i18n = useI18n();
|
||||
const projectsStore = useProjectsStore();
|
||||
const sourceControlStore = useSourceControlStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const overview = useOverview();
|
||||
const projectPages = useProjectPages();
|
||||
|
||||
const emit = defineEmits<{
|
||||
createFolder: [];
|
||||
@@ -39,7 +39,12 @@ const headerIcon = computed((): ProjectIconType => {
|
||||
|
||||
const projectName = computed(() => {
|
||||
if (!projectsStore.currentProject) {
|
||||
return i18n.baseText('projects.menu.overview');
|
||||
if (projectPages.isOverviewSubPage) {
|
||||
return i18n.baseText('projects.menu.overview');
|
||||
} else if (projectPages.isSharedSubPage) {
|
||||
return i18n.baseText('projects.header.shared.title');
|
||||
}
|
||||
return null;
|
||||
} else if (projectsStore.currentProject.type === ProjectTypes.Personal) {
|
||||
return i18n.baseText('projects.menu.personal');
|
||||
} else {
|
||||
@@ -60,6 +65,10 @@ const showSettings = computed(
|
||||
|
||||
const homeProject = computed(() => projectsStore.currentProject ?? projectsStore.personalProject);
|
||||
|
||||
const isPersonalProject = computed(() => {
|
||||
return homeProject.value?.type === ProjectTypes.Personal;
|
||||
});
|
||||
|
||||
const showFolders = computed(() => {
|
||||
return (
|
||||
settingsStore.isFoldersFeatureEnabled &&
|
||||
@@ -83,6 +92,7 @@ const createWorkflowButton = computed(() => ({
|
||||
sourceControlStore.preferences.branchReadOnly ||
|
||||
!getResourcePermissions(homeProject.value?.scopes).workflow.create,
|
||||
}));
|
||||
|
||||
const menu = computed(() => {
|
||||
const items: UserAction[] = [
|
||||
{
|
||||
@@ -105,6 +115,12 @@ const menu = computed(() => {
|
||||
return items;
|
||||
});
|
||||
|
||||
const showProjectIcon = computed(() => {
|
||||
return (
|
||||
!projectPages.isOverviewSubPage && !projectPages.isSharedSubPage && !isPersonalProject.value
|
||||
);
|
||||
});
|
||||
|
||||
const actions: Record<ActionTypes, (projectId: string) => void> = {
|
||||
[ACTION_TYPES.WORKFLOW]: (projectId: string) => {
|
||||
void router.push({
|
||||
@@ -129,6 +145,27 @@ const actions: Record<ActionTypes, (projectId: string) => void> = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
const pageType = computed(() => {
|
||||
if (projectPages.isOverviewSubPage) {
|
||||
return 'overview';
|
||||
} else if (projectPages.isSharedSubPage) {
|
||||
return 'shared';
|
||||
} else {
|
||||
return 'project';
|
||||
}
|
||||
});
|
||||
|
||||
const subtitle = computed(() => {
|
||||
if (projectPages.isOverviewSubPage) {
|
||||
return i18n.baseText('projects.header.overview.subtitle');
|
||||
} else if (projectPages.isSharedSubPage) {
|
||||
return i18n.baseText('projects.header.shared.subtitle');
|
||||
} else if (isPersonalProject.value) {
|
||||
return i18n.baseText('projects.header.personal.subtitle');
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const onSelect = (action: string) => {
|
||||
const executableAction = actions[action as ActionTypes];
|
||||
if (!homeProject.value) {
|
||||
@@ -142,19 +179,16 @@ const onSelect = (action: string) => {
|
||||
<div>
|
||||
<div :class="$style.projectHeader">
|
||||
<div :class="$style.projectDetails">
|
||||
<ProjectIcon
|
||||
v-if="!overview.isOverviewSubPage"
|
||||
:icon="headerIcon"
|
||||
:border-less="true"
|
||||
size="medium"
|
||||
/>
|
||||
<ProjectIcon v-if="showProjectIcon" :icon="headerIcon" :border-less="true" size="medium" />
|
||||
<div :class="$style.headerActions">
|
||||
<N8nHeading bold tag="h2" size="xlarge">{{ projectName }}</N8nHeading>
|
||||
<N8nHeading v-if="projectName" bold tag="h2" size="xlarge" data-test-id="project-name">{{
|
||||
projectName
|
||||
}}</N8nHeading>
|
||||
<N8nText color="text-light">
|
||||
<slot name="subtitle">
|
||||
<span v-if="!projectsStore.currentProject">{{
|
||||
i18n.baseText('projects.header.subtitle')
|
||||
}}</span>
|
||||
<N8nText v-if="subtitle" color="text-light" data-test-id="project-subtitle">{{
|
||||
subtitle
|
||||
}}</N8nText>
|
||||
</slot>
|
||||
</N8nText>
|
||||
</div>
|
||||
@@ -181,7 +215,11 @@ const onSelect = (action: string) => {
|
||||
</div>
|
||||
<slot></slot>
|
||||
<div :class="$style.actions">
|
||||
<ProjectTabs :show-settings="showSettings" />
|
||||
<ProjectTabs
|
||||
:page-type="pageType"
|
||||
:show-executions="!projectPages.isSharedSubPage"
|
||||
:show-settings="showSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -23,7 +23,6 @@ const settingsStore = useSettingsStore();
|
||||
|
||||
const isCreatingProject = computed(() => globalEntityCreation.isCreatingProject.value);
|
||||
const displayProjects = computed(() => globalEntityCreation.displayProjects.value);
|
||||
// TODO: Once we remove the feature flag, we can remove this computed property
|
||||
const isFoldersFeatureEnabled = computed(() => settingsStore.isFoldersFeatureEnabled);
|
||||
|
||||
const home = computed<IMenuItem>(() => ({
|
||||
@@ -35,6 +34,15 @@ const home = computed<IMenuItem>(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const shared = computed<IMenuItem>(() => ({
|
||||
id: 'shared',
|
||||
label: locale.baseText('projects.menu.shared'),
|
||||
icon: 'share',
|
||||
route: {
|
||||
to: { name: VIEWS.SHARED_WITH_ME },
|
||||
},
|
||||
}));
|
||||
|
||||
const getProjectMenuItem = (project: ProjectListItem) => ({
|
||||
id: project.id,
|
||||
label: project.name,
|
||||
@@ -74,6 +82,22 @@ const showAddFirstProject = computed(
|
||||
mode="tabs"
|
||||
data-test-id="project-home-menu-item"
|
||||
/>
|
||||
<N8nMenuItem
|
||||
v-if="projectsStore.isTeamProjectFeatureEnabled || isFoldersFeatureEnabled"
|
||||
:item="personalProject"
|
||||
:compact="props.collapsed"
|
||||
:active-tab="projectsStore.projectNavActiveId"
|
||||
mode="tabs"
|
||||
data-test-id="project-personal-menu-item"
|
||||
/>
|
||||
<N8nMenuItem
|
||||
v-if="projectsStore.isTeamProjectFeatureEnabled || isFoldersFeatureEnabled"
|
||||
:item="shared"
|
||||
:compact="props.collapsed"
|
||||
:active-tab="projectsStore.projectNavActiveId"
|
||||
mode="tabs"
|
||||
data-test-id="project-shared-menu-item"
|
||||
/>
|
||||
</ElMenu>
|
||||
<hr v-if="projectsStore.isTeamProjectFeatureEnabled" class="mt-m mb-m" />
|
||||
<N8nText
|
||||
@@ -104,13 +128,6 @@ const showAddFirstProject = computed(
|
||||
:collapse="props.collapsed"
|
||||
:class="$style.projectItems"
|
||||
>
|
||||
<N8nMenuItem
|
||||
:item="personalProject"
|
||||
:compact="props.collapsed"
|
||||
:active-tab="projectsStore.projectNavActiveId"
|
||||
mode="tabs"
|
||||
data-test-id="project-personal-menu-item"
|
||||
/>
|
||||
<N8nMenuItem
|
||||
v-for="project in displayProjects"
|
||||
:key="project.id"
|
||||
|
||||
@@ -4,75 +4,106 @@ import type { RouteRecordName } from 'vue-router';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { VIEWS } from '@/constants';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import type { BaseTextKey } from '@/plugins/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
type Props = {
|
||||
showSettings?: boolean;
|
||||
}>();
|
||||
showExecutions?: boolean;
|
||||
pageType?: 'overview' | 'shared' | 'project';
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showSettings: false,
|
||||
showExecutions: true,
|
||||
pageType: 'project',
|
||||
});
|
||||
|
||||
const locale = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const selectedTab = ref<RouteRecordName | null | undefined>('');
|
||||
|
||||
const projectId = computed(() => {
|
||||
return Array.isArray(route?.params?.projectId)
|
||||
? route.params.projectId[0]
|
||||
: route?.params?.projectId;
|
||||
});
|
||||
|
||||
const getRouteConfigs = () => {
|
||||
// For project pages
|
||||
if (projectId.value) {
|
||||
return {
|
||||
workflows: {
|
||||
name: VIEWS.PROJECTS_WORKFLOWS,
|
||||
params: { projectId: projectId.value },
|
||||
},
|
||||
credentials: {
|
||||
name: VIEWS.PROJECTS_CREDENTIALS,
|
||||
params: { projectId: projectId.value },
|
||||
},
|
||||
executions: {
|
||||
name: VIEWS.PROJECTS_EXECUTIONS,
|
||||
params: { projectId: projectId.value },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Shared with me
|
||||
if (props.pageType === 'shared') {
|
||||
return {
|
||||
workflows: { name: VIEWS.SHARED_WORKFLOWS },
|
||||
credentials: { name: VIEWS.SHARED_CREDENTIALS },
|
||||
executions: { name: VIEWS.NOT_FOUND },
|
||||
};
|
||||
}
|
||||
|
||||
// Overview
|
||||
return {
|
||||
workflows: { name: VIEWS.WORKFLOWS },
|
||||
credentials: { name: VIEWS.CREDENTIALS },
|
||||
executions: { name: VIEWS.EXECUTIONS },
|
||||
};
|
||||
};
|
||||
|
||||
// Create individual tab objects
|
||||
const createTab = (
|
||||
label: BaseTextKey,
|
||||
routeKey: string,
|
||||
routes: Record<string, { name: RouteRecordName; params?: Record<string, string | number> }>,
|
||||
) => {
|
||||
return {
|
||||
label: locale.baseText(label),
|
||||
value: routes[routeKey].name,
|
||||
to: routes[routeKey],
|
||||
};
|
||||
};
|
||||
|
||||
// Generate the tabs configuration
|
||||
const options = computed(() => {
|
||||
const projectId = route?.params?.projectId;
|
||||
const to = projectId
|
||||
? {
|
||||
workflows: {
|
||||
name: VIEWS.PROJECTS_WORKFLOWS,
|
||||
params: { projectId },
|
||||
},
|
||||
credentials: {
|
||||
name: VIEWS.PROJECTS_CREDENTIALS,
|
||||
params: { projectId },
|
||||
},
|
||||
executions: {
|
||||
name: VIEWS.PROJECTS_EXECUTIONS,
|
||||
params: { projectId },
|
||||
},
|
||||
}
|
||||
: {
|
||||
workflows: {
|
||||
name: VIEWS.WORKFLOWS,
|
||||
},
|
||||
credentials: {
|
||||
name: VIEWS.CREDENTIALS,
|
||||
},
|
||||
executions: {
|
||||
name: VIEWS.EXECUTIONS,
|
||||
},
|
||||
};
|
||||
const routes = getRouteConfigs();
|
||||
const tabs = [
|
||||
{
|
||||
label: locale.baseText('mainSidebar.workflows'),
|
||||
value: to.workflows.name,
|
||||
to: to.workflows,
|
||||
},
|
||||
{
|
||||
label: locale.baseText('mainSidebar.credentials'),
|
||||
value: to.credentials.name,
|
||||
to: to.credentials,
|
||||
},
|
||||
{
|
||||
label: locale.baseText('mainSidebar.executions'),
|
||||
value: to.executions.name,
|
||||
to: to.executions,
|
||||
},
|
||||
createTab('mainSidebar.workflows', 'workflows', routes),
|
||||
createTab('mainSidebar.credentials', 'credentials', routes),
|
||||
];
|
||||
|
||||
if (props.showExecutions) {
|
||||
tabs.push(createTab('mainSidebar.executions', 'executions', routes));
|
||||
}
|
||||
|
||||
if (props.showSettings) {
|
||||
tabs.push({
|
||||
label: locale.baseText('projects.settings'),
|
||||
value: VIEWS.PROJECT_SETTINGS,
|
||||
to: { name: VIEWS.PROJECT_SETTINGS, params: { projectId } },
|
||||
to: { name: VIEWS.PROJECT_SETTINGS, params: { projectId: projectId.value } },
|
||||
});
|
||||
}
|
||||
|
||||
return tabs;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route?.name,
|
||||
() => {
|
||||
selectedTab.value = route?.name;
|
||||
// Select workflows tab if folders tab is selected
|
||||
selectedTab.value =
|
||||
route.name === VIEWS.PROJECTS_FOLDERS ? VIEWS.PROJECTS_WORKFLOWS : route.name;
|
||||
|
||||
@@ -297,14 +297,11 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route?.params?.projectId,
|
||||
async () => {
|
||||
await resetFilters();
|
||||
await loadPaginationPreferences();
|
||||
await props.initialize();
|
||||
},
|
||||
);
|
||||
watch([() => route.params?.projectId, () => route.name], async () => {
|
||||
await resetFilters();
|
||||
await loadPaginationPreferences();
|
||||
await props.initialize();
|
||||
});
|
||||
|
||||
// Lifecycle hooks
|
||||
onMounted(async () => {
|
||||
|
||||
Reference in New Issue
Block a user