Skip to content

Commit

Permalink
fix(editor): Consistent protected environment styling and messaging (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
r00gm authored Dec 27, 2024
1 parent 983e87a commit 6891cef
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface ActionBoxProps {
buttonText: string;
buttonType: ButtonType;
buttonDisabled?: boolean;
buttonIcon?: string;
description: string;
calloutText?: string;
calloutTheme?: CalloutTheme;
Expand All @@ -22,6 +23,7 @@ interface ActionBoxProps {
defineOptions({ name: 'N8nActionBox' });
withDefaults(defineProps<ActionBoxProps>(), {
calloutTheme: 'info',
buttonIcon: undefined,
});
</script>

Expand Down Expand Up @@ -51,6 +53,7 @@ withDefaults(defineProps<ActionBoxProps>(), {
:label="buttonText"
:type="buttonType"
:disabled="buttonDisabled"
:icon="buttonIcon"
size="large"
@click="$emit('click:button', $event)"
/>
Expand Down
1 change: 1 addition & 0 deletions packages/editor-ui/src/components/Logo/Logo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ onMounted(() => {
<div v-if="showReleaseChannelTag" size="small" round :class="$style.releaseChannelTag">
{{ releaseChannel }}
</div>
<slot />
</div>
</template>

Expand Down
55 changes: 53 additions & 2 deletions packages/editor-ui/src/components/MainSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useUIStore } from '@/stores/ui.store';
import { useUsersStore } from '@/stores/users.store';
import { useVersionsStore } from '@/stores/versions.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { hasPermission } from '@/utils/rbac/permissions';
import { useDebounce } from '@/composables/useDebounce';
Expand All @@ -23,7 +24,7 @@ import { useBugReporting } from '@/composables/useBugReporting';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
import { useGlobalEntityCreation } from '@/composables/useGlobalEntityCreation';
import { N8nNavigationDropdown } from 'n8n-design-system';
import { N8nNavigationDropdown, N8nTooltip, N8nLink, N8nIconButton } from 'n8n-design-system';
import { onClickOutside, type VueInstance } from '@vueuse/core';
import Logo from './Logo/Logo.vue';
Expand All @@ -36,6 +37,7 @@ const uiStore = useUIStore();
const usersStore = useUsersStore();
const versionsStore = useVersionsStore();
const workflowsStore = useWorkflowsStore();
const sourceControlStore = useSourceControlStore();
const { callDebounced } = useDebounce();
const externalHooks = useExternalHooks();
Expand Down Expand Up @@ -292,6 +294,8 @@ const {
menu,
handleSelect: handleMenuSelect,
createProjectAppendSlotName,
createWorkflowsAppendSlotName,
createCredentialsAppendSlotName,
projectsLimitReachedMessage,
upgradeLabel,
} = useGlobalEntityCreation();
Expand Down Expand Up @@ -322,14 +326,51 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
location="sidebar"
:collapsed="isCollapsed"
:release-channel="settingsStore.settings.releaseChannel"
/>
>
<N8nTooltip
v-if="sourceControlStore.preferences.branchReadOnly && !isCollapsed"
placement="bottom"
>
<template #content>
<i18n-t keypath="readOnlyEnv.tooltip">
<template #link>
<N8nLink
to="https://docs.n8n.io/source-control-environments/setup/#step-4-connect-n8n-and-configure-your-instance"
size="small"
>
{{ i18n.baseText('readOnlyEnv.tooltip.link') }}
</N8nLink>
</template>
</i18n-t>
</template>
<N8nIcon icon="lock" size="xsmall" :class="$style.readOnlyEnvironmentIcon" />
</N8nTooltip>
</Logo>
<N8nNavigationDropdown
ref="createBtn"
data-test-id="universal-add"
:menu="menu"
@select="handleMenuSelect"
>
<N8nIconButton icon="plus" type="secondary" outline />
<template #[createWorkflowsAppendSlotName]>
<N8nTooltip
v-if="sourceControlStore.preferences.branchReadOnly"
placement="right"
:content="i18n.baseText('readOnlyEnv.cantAdd.workflow')"
>
<N8nIcon style="margin-left: auto; margin-right: 5px" icon="lock" size="xsmall" />
</N8nTooltip>
</template>
<template #[createCredentialsAppendSlotName]>
<N8nTooltip
v-if="sourceControlStore.preferences.branchReadOnly"
placement="right"
:content="i18n.baseText('readOnlyEnv.cantAdd.credential')"
>
<N8nIcon style="margin-left: auto; margin-right: 5px" icon="lock" size="xsmall" />
</N8nTooltip>
</template>
<template #[createProjectAppendSlotName]="{ item }">
<N8nTooltip v-if="item.disabled" placement="right" :content="projectsLimitReachedMessage">
<N8nButton
Expand Down Expand Up @@ -544,4 +585,14 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
display: none;
}
}
.readOnlyEnvironmentIcon {
display: inline-block;
color: white;
background-color: var(--color-warning);
align-self: center;
padding: 2px;
border-radius: var(--border-radius-small);
margin: 5px 5px 0;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Action = {
};
defineProps<{
actions: Action[];
disabled?: boolean;
}>();
const emit = defineEmits<{
Expand All @@ -25,7 +26,7 @@ const emit = defineEmits<{
:teleported="false"
@action="emit('action', $event)"
>
<N8nIconButton :class="[$style.buttonGroupDropdown]" icon="angle-down" />
<N8nIconButton :disabled="disabled" :class="[$style.buttonGroupDropdown]" icon="angle-down" />
</N8nActionToggle>
</div>
</template>
Expand Down
30 changes: 19 additions & 11 deletions packages/editor-ui/src/components/Projects/ProjectHeader.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { N8nButton } from 'n8n-design-system';
import { N8nButton, N8nTooltip } from 'n8n-design-system';
import { useI18n } from '@/composables/useI18n';
import { ProjectTypes } from '@/types/projects.types';
import { useProjectsStore } from '@/stores/projects.store';
Expand Down Expand Up @@ -59,6 +59,8 @@ type ActionTypes = (typeof ACTION_TYPES)[keyof typeof ACTION_TYPES];
const createWorkflowButton = computed(() => ({
value: ACTION_TYPES.WORKFLOW,
label: 'Create Workflow',
icon: sourceControlStore.preferences.branchReadOnly ? 'lock' : undefined,
size: 'mini' as const,
disabled:
sourceControlStore.preferences.branchReadOnly ||
!getResourcePermissions(homeProject.value?.scopes).workflow.create,
Expand Down Expand Up @@ -119,17 +121,23 @@ const onSelect = (action: string) => {
</N8nText>
</div>
<div v-if="route.name !== VIEWS.PROJECT_SETTINGS" :class="[$style.headerActions]">
<ProjectCreateResource
data-test-id="add-resource-buttons"
:actions="menu"
@action="onSelect"
<N8nTooltip
:disabled="!sourceControlStore.preferences.branchReadOnly"
:content="i18n.baseText('readOnlyEnv.cantAdd.any')"
>
<N8nButton
data-test-id="add-resource-workflow"
v-bind="createWorkflowButton"
@click="onSelect(ACTION_TYPES.WORKFLOW)"
/>
</ProjectCreateResource>
<ProjectCreateResource
data-test-id="add-resource-buttons"
:actions="menu"
:disabled="sourceControlStore.preferences.branchReadOnly"
@action="onSelect"
>
<N8nButton
data-test-id="add-resource-workflow"
v-bind="createWorkflowButton"
@click="onSelect(ACTION_TYPES.WORKFLOW)"
/>
</ProjectCreateResource>
</N8nTooltip>
</div>
</div>
<div :class="$style.actions">
Expand Down
25 changes: 25 additions & 0 deletions packages/editor-ui/src/composables/useGlobalEntityCreation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useToast } from '@/composables/useToast';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
import { useSettingsStore } from '@/stores/settings.store';
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import type { CloudPlanState } from '@/Interface';

import { VIEWS } from '@/constants';
Expand Down Expand Up @@ -179,4 +180,28 @@ describe('useGlobalEntityCreation', () => {
);
expect(upgradeLabel.value).toBe('Enterprise');
});

it('should display properly for readOnlyEnvironment', () => {
const sourceControlStore = mockedStore(useSourceControlStore);
sourceControlStore.preferences.branchReadOnly = true;
const projectsStore = mockedStore(useProjectsStore);
projectsStore.teamProjectsLimit = -1;

const personalProjectId = 'personal-project';
projectsStore.isTeamProjectFeatureEnabled = true;
projectsStore.personalProject = { id: personalProjectId } as Project;
projectsStore.myProjects = [
{ id: '1', name: '1', type: 'team' },
{ id: '2', name: '2', type: 'public' },
{ id: '3', name: '3', type: 'team' },
] as ProjectListItem[];

const { menu } = useGlobalEntityCreation();

expect(menu.value[0].disabled).toBe(true);
expect(menu.value[1].disabled).toBe(true);

expect(menu.value[0].submenu).toBe(undefined);
expect(menu.value[1].submenu).toBe(undefined);
});
});
117 changes: 65 additions & 52 deletions packages/editor-ui/src/composables/useGlobalEntityCreation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type Item = BaseItem & {

export const useGlobalEntityCreation = () => {
const CREATE_PROJECT_ID = 'create-project';
const WORKFLOWS_MENU_ID = 'workflow';
const CREDENTIALS_MENU_ID = 'credential';

const settingsStore = useSettingsStore();
const cloudPlanStore = useCloudPlanStore();
Expand Down Expand Up @@ -89,66 +91,73 @@ export const useGlobalEntityCreation = () => {
// global
return [
{
id: 'workflow',
id: WORKFLOWS_MENU_ID,
title: 'Workflow',
submenu: [
{
id: 'workflow-title',
title: 'Create in',
disabled: true,
},
{
id: 'workflow-personal',
title: i18n.baseText('projects.menu.personal'),
icon: 'user',
disabled: disabledWorkflow(projectsStore.personalProject?.scopes),
route: {
name: VIEWS.NEW_WORKFLOW,
query: { projectId: projectsStore.personalProject?.id },
disabled: sourceControlStore.preferences.branchReadOnly,

...(!sourceControlStore.preferences.branchReadOnly && {
submenu: [
{
id: 'workflow-title',
title: 'Create in',
disabled: true,
},
},
...displayProjects.value.map((project) => ({
id: `workflow-${project.id}`,
title: project.name as string,
icon: 'layer-group',
disabled: disabledWorkflow(project.scopes),
route: {
name: VIEWS.NEW_WORKFLOW,
query: { projectId: project.id },
{
id: 'workflow-personal',
title: i18n.baseText('projects.menu.personal'),
icon: 'user',
disabled: disabledWorkflow(projectsStore.personalProject?.scopes),
route: {
name: VIEWS.NEW_WORKFLOW,
query: { projectId: projectsStore.personalProject?.id },
},
},
})),
],
...displayProjects.value.map((project) => ({
id: `workflow-${project.id}`,
title: project.name as string,
icon: 'layer-group',
disabled: disabledWorkflow(project.scopes),
route: {
name: VIEWS.NEW_WORKFLOW,
query: { projectId: project.id },
},
})),
],
}),
},
{
id: 'credential',
id: CREDENTIALS_MENU_ID,
title: 'Credential',
submenu: [
{
id: 'credential-title',
title: 'Create in',
disabled: true,
},
{
id: 'credential-personal',
title: i18n.baseText('projects.menu.personal'),
icon: 'user',
disabled: disabledCredential(projectsStore.personalProject?.scopes),
route: {
name: VIEWS.PROJECTS_CREDENTIALS,
params: { projectId: projectsStore.personalProject?.id, credentialId: 'create' },
disabled: sourceControlStore.preferences.branchReadOnly,
...(!sourceControlStore.preferences.branchReadOnly && {
submenu: [
{
id: 'credential-title',
title: 'Create in',
disabled: true,
},
},
...displayProjects.value.map((project) => ({
id: `credential-${project.id}`,
title: project.name as string,
icon: 'layer-group',
disabled: disabledCredential(project.scopes),
route: {
name: VIEWS.PROJECTS_CREDENTIALS,
params: { projectId: project.id, credentialId: 'create' },
{
id: 'credential-personal',
title: i18n.baseText('projects.menu.personal'),
icon: 'user',
disabled: disabledCredential(projectsStore.personalProject?.scopes),
route: {
name: VIEWS.PROJECTS_CREDENTIALS,
params: { projectId: projectsStore.personalProject?.id, credentialId: 'create' },
},
},
})),
],
...displayProjects.value.map((project) => ({
id: `credential-${project.id}`,
title: project.name as string,
icon: 'layer-group',
disabled: disabledCredential(project.scopes),
route: {
name: VIEWS.PROJECTS_CREDENTIALS,
params: { projectId: project.id, credentialId: 'create' },
},
})),
],
}),
},
{
id: CREATE_PROJECT_ID,
Expand Down Expand Up @@ -214,6 +223,8 @@ export const useGlobalEntityCreation = () => {
});

const createProjectAppendSlotName = computed(() => `item.append.${CREATE_PROJECT_ID}`);
const createWorkflowsAppendSlotName = computed(() => `item.append.${WORKFLOWS_MENU_ID}`);
const createCredentialsAppendSlotName = computed(() => `item.append.${CREDENTIALS_MENU_ID}`);

const upgradeLabel = computed(() => {
if (settingsStore.isCloudDeployment) {
Expand All @@ -231,6 +242,8 @@ export const useGlobalEntityCreation = () => {
menu,
handleSelect,
createProjectAppendSlotName,
createWorkflowsAppendSlotName,
createCredentialsAppendSlotName,
projectsLimitReachedMessage,
upgradeLabel,
createProject,
Expand Down
Loading

0 comments on commit 6891cef

Please sign in to comment.