fix(editor): Add Set up version control CTA (#6356)

* fix(editor): Add Set up version control CTA

* fix(editor): add unit test

* fix(editor): extend unit test

* fix(editor): update menu sidebar styles

* fix(editor): update menu sidebar styles

* fix(editor): fixes after conflict

* fix(editor): hide branch color when not connected

* fix(editor): fix connected collapsed paddings
This commit is contained in:
Csaba Tuncsik 2023-06-06 14:27:26 +02:00 committed by GitHub
parent 97295f67f0
commit e72521d5ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 215 additions and 81 deletions

View file

@ -193,9 +193,4 @@ export default defineComponent({
display: none !important; display: none !important;
} }
} }
.menuPrefix,
.menuSuffix {
padding: var(--spacing-xs) var(--spacing-l);
}
</style> </style>

View file

@ -32,7 +32,7 @@
v-if="!isCollapsed && userIsTrialing" v-if="!isCollapsed && userIsTrialing"
/></template> /></template>
<template #menuSuffix> <template #menuSuffix>
<div v-if="hasVersionUpdates || versionControlStore.preferences.connected"> <div>
<div v-if="hasVersionUpdates" :class="$style.updates" @click="openUpdatesPanel"> <div v-if="hasVersionUpdates" :class="$style.updates" @click="openUpdatesPanel">
<div :class="$style.giftContainer"> <div :class="$style.giftContainer">
<GiftNotificationIcon /> <GiftNotificationIcon />
@ -46,10 +46,7 @@
}} }}
</n8n-text> </n8n-text>
</div> </div>
<MainSidebarVersionControl <MainSidebarVersionControl :is-collapsed="isCollapsed" />
v-if="versionControlStore.preferences.connected"
:is-collapsed="isCollapsed"
/>
</div> </div>
</template> </template>
<template #footer v-if="showUserArea"> <template #footer v-if="showUserArea">
@ -115,15 +112,17 @@ import { userHelpers } from '@/mixins/userHelpers';
import { debounceHelper } from '@/mixins/debounce'; import { debounceHelper } from '@/mixins/debounce';
import Vue, { defineComponent } from 'vue'; import Vue, { defineComponent } from 'vue';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui.store'; import {
import { useSettingsStore } from '@/stores/settings.store'; useUIStore,
import { useUsersStore } from '@/stores/users.store'; useSettingsStore,
import { useWorkflowsStore } from '@/stores/workflows.store'; useUsersStore,
import { useRootStore } from '@/stores/n8nRoot.store'; useWorkflowsStore,
import { useVersionsStore } from '@/stores/versions.store'; useRootStore,
useVersionsStore,
useCloudPlanStore,
useVersionControlStore,
} from '@/stores/';
import { isNavigationFailure } from 'vue-router'; import { isNavigationFailure } from 'vue-router';
import { useVersionControlStore } from '@/stores/versionControl.store';
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
import ExecutionsUsage from '@/components/ExecutionsUsage.vue'; import ExecutionsUsage from '@/components/ExecutionsUsage.vue';
import MainSidebarVersionControl from '@/components/MainSidebarVersionControl.vue'; import MainSidebarVersionControl from '@/components/MainSidebarVersionControl.vue';
@ -155,8 +154,8 @@ export default defineComponent({
useUsersStore, useUsersStore,
useVersionsStore, useVersionsStore,
useWorkflowsStore, useWorkflowsStore,
useVersionControlStore,
useCloudPlanStore, useCloudPlanStore,
useVersionControlStore,
), ),
hasVersionUpdates(): boolean { hasVersionUpdates(): boolean {
return this.versionsStore.hasVersionUpdates; return this.versionsStore.hasVersionUpdates;
@ -543,8 +542,9 @@ export default defineComponent({
.updates { .updates {
display: flex; display: flex;
align-items: center; align-items: center;
height: 26px;
cursor: pointer; cursor: pointer;
padding: var(--spacing-2xs) var(--spacing-l);
margin: var(--spacing-2xs) 0 0;
svg { svg {
color: var(--color-text-base) !important; color: var(--color-text-base) !important;

View file

@ -1,18 +1,20 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useVersionControlStore } from '@/stores/versionControl.store';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n, useLoadingService, useMessage, useToast } from '@/composables'; import { useRouter } from 'vue-router/composables';
import { useUIStore } from '@/stores';
import { VERSION_CONTROL_PUSH_MODAL_KEY } from '@/constants';
import { createEventBus } from 'n8n-design-system/utils'; import { createEventBus } from 'n8n-design-system/utils';
import { useI18n, useLoadingService, useMessage, useToast } from '@/composables';
import { useUIStore, useUsersStore, useVersionControlStore } from '@/stores';
import { VERSION_CONTROL_PUSH_MODAL_KEY, VIEWS } from '@/constants';
const props = defineProps<{ const props = defineProps<{
isCollapsed: boolean; isCollapsed: boolean;
}>(); }>();
const router = useRouter();
const loadingService = useLoadingService(); const loadingService = useLoadingService();
const uiStore = useUIStore(); const uiStore = useUIStore();
const versionControlStore = useVersionControlStore(); const versionControlStore = useVersionControlStore();
const usersStore = useUsersStore();
const message = useMessage(); const message = useMessage();
const toast = useToast(); const toast = useToast();
const { i18n } = useI18n(); const { i18n } = useI18n();
@ -24,6 +26,8 @@ const currentBranch = computed(() => {
return versionControlStore.preferences.branchName; return versionControlStore.preferences.branchName;
}); });
const setupButtonTooltipPlacement = computed(() => (props.isCollapsed ? 'right' : 'top'));
async function pushWorkfolder() { async function pushWorkfolder() {
loadingService.startLoading(); loadingService.startLoading();
try { try {
@ -68,76 +72,127 @@ async function pullWorkfolder() {
loadingService.setLoadingText(i18n.baseText('genericHelpers.loading')); loadingService.setLoadingText(i18n.baseText('genericHelpers.loading'));
} }
} }
const goToVersionControlSetup = async () => {
await router.push({ name: VIEWS.VERSION_CONTROL });
};
</script> </script>
<template> <template>
<div <div
:class="{ [$style.sync]: true, [$style.collapsed]: isCollapsed }" :class="{
[$style.sync]: true,
[$style.collapsed]: isCollapsed,
[$style.isConnected]:
versionControlStore.preferences.connected && versionControlStore.preferences.branchName,
}"
:style="{ borderLeftColor: versionControlStore.preferences.branchColor }" :style="{ borderLeftColor: versionControlStore.preferences.branchColor }"
data-test-id="main-sidebar-version-control"
> >
<span> <div
<n8n-icon icon="code-branch" /> v-if="versionControlStore.preferences.connected && versionControlStore.preferences.branchName"
{{ currentBranch }} :class="$style.connected"
</span> data-test-id="main-sidebar-version-control-connected"
<div :class="{ 'pt-xs': !isCollapsed }"> >
<n8n-tooltip :disabled="!isCollapsed" :open-delay="tooltipOpenDelay" placement="right"> <span>
<template #content> <n8n-icon icon="code-branch" />
<div> {{ currentBranch }}
{{ i18n.baseText('settings.versionControl.button.pull') }} </span>
</div> <div :class="{ 'pt-xs': !isCollapsed }">
</template> <n8n-tooltip :disabled="!isCollapsed" :open-delay="tooltipOpenDelay" placement="right">
<n8n-button <template #content>
:class="{ <div>
'mr-2xs': !isCollapsed, {{ i18n.baseText('settings.versionControl.button.pull') }}
'mb-2xs': isCollapsed && !versionControlStore.preferences.branchReadOnly, </div>
}" </template>
icon="arrow-down" <n8n-button
type="tertiary" :class="{
size="mini" 'mr-2xs': !isCollapsed,
:square="isCollapsed" 'mb-2xs': isCollapsed && !versionControlStore.preferences.branchReadOnly,
@click="pullWorkfolder" }"
icon="arrow-down"
type="tertiary"
size="mini"
:square="isCollapsed"
@click="pullWorkfolder"
>
<span v-if="!isCollapsed">{{
i18n.baseText('settings.versionControl.button.pull')
}}</span>
</n8n-button>
</n8n-tooltip>
<n8n-tooltip
v-if="!versionControlStore.preferences.branchReadOnly"
:disabled="!isCollapsed"
:open-delay="tooltipOpenDelay"
placement="right"
> >
<span v-if="!isCollapsed">{{ <template #content>
i18n.baseText('settings.versionControl.button.pull') <div>
}}</span> {{ i18n.baseText('settings.versionControl.button.push') }}
</n8n-button> </div>
</n8n-tooltip> </template>
<n8n-tooltip <n8n-button
v-if="!versionControlStore.preferences.branchReadOnly" :square="isCollapsed"
:disabled="!isCollapsed" icon="arrow-up"
:open-delay="tooltipOpenDelay" type="tertiary"
placement="right" size="mini"
> @click="pushWorkfolder"
<template #content> >
<div> <span v-if="!isCollapsed">{{
{{ i18n.baseText('settings.versionControl.button.push') }} i18n.baseText('settings.versionControl.button.push')
</div> }}</span>
</template> </n8n-button>
<n8n-button </n8n-tooltip>
:square="isCollapsed" </div>
icon="arrow-up"
type="tertiary"
size="mini"
@click="pushWorkfolder"
>
<span v-if="!isCollapsed">{{
i18n.baseText('settings.versionControl.button.push')
}}</span>
</n8n-button>
</n8n-tooltip>
</div> </div>
<n8n-tooltip
v-else-if="
versionControlStore.isEnterpriseVersionControlEnabled && usersStore.isInstanceOwner
"
:open-delay="tooltipOpenDelay"
:placement="setupButtonTooltipPlacement"
data-test-id="main-sidebar-version-control-setup"
>
<template #content>
<div>
{{ i18n.baseText('settings.versionControl.button.setup.tooltip') }}
</div>
</template>
<n8n-button
icon="code-branch"
type="tertiary"
size="mini"
:square="isCollapsed"
@click="goToVersionControlSetup"
>
<span v-if="!isCollapsed">{{ i18n.baseText('settings.versionControl.button.setup') }}</span>
</n8n-button>
</n8n-tooltip>
</div> </div>
</template> </template>
<style lang="scss" module> <style lang="scss" module>
.sync { .sync {
padding: var(--spacing-s) var(--spacing-s) var(--spacing-s) var(--spacing-m); padding: var(--spacing-s) var(--spacing-s) var(--spacing-s) var(--spacing-l);
margin: 0 calc(var(--spacing-l) * -1) calc(var(--spacing-m) * -1); margin: var(--spacing-2xs) 0 calc(var(--spacing-2xs) * -1);
background: var(--color-background-light); background: var(--color-background-light);
border-top: var(--border-width-base) var(--border-style-base) var(--color-foreground-base); border-top: var(--border-width-base) var(--border-style-base) var(--color-foreground-base);
border-left: var(--spacing-3xs) var(--border-style-base) var(--color-foreground-base);
font-size: var(--font-size-2xs); font-size: var(--font-size-2xs);
&.isConnected {
padding-left: var(--spacing-m);
border-left: var(--spacing-3xs) var(--border-style-base) var(--color-foreground-base);
&.collapsed {
padding-left: var(--spacing-xs);
}
}
&:empty {
display: none;
}
span { span {
color: var(--color-text-base); color: var(--color-text-base);
} }
@ -149,10 +204,13 @@ async function pullWorkfolder() {
.collapsed { .collapsed {
text-align: center; text-align: center;
padding-left: var(--spacing-xs); padding-left: var(--spacing-s);
padding-right: var(--spacing-s);
> span { .connected {
display: none; > span {
display: none;
}
} }
} }
</style> </style>

View file

@ -246,8 +246,11 @@ export default defineComponent({
} }
} }
.versionContainer {
padding: var(--spacing-xs) var(--spacing-l);
}
@media screen and (max-height: 420px) { @media screen and (max-height: 420px) {
.updatesSubmenu,
.versionContainer { .versionContainer {
display: none; display: none;
} }

View file

@ -0,0 +1,76 @@
import { describe, it, expect, vi } from 'vitest';
import { render } from '@testing-library/vue';
import { PiniaVuePlugin } from 'pinia';
import { createTestingPinia } from '@pinia/testing';
import { STORES } from '@/constants';
import { i18nInstance } from '@/plugins/i18n';
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
import MainSidebarVersionControl from '@/components/MainSidebarVersionControl.vue';
import { useUsersStore, useVersionControlStore } from '@/stores';
import { merge } from 'lodash-es';
let pinia: ReturnType<typeof createTestingPinia>;
let versionControlStore: ReturnType<typeof useVersionControlStore>;
let usersStore: ReturnType<typeof useUsersStore>;
const renderComponent = (renderOptions: Parameters<typeof render>[1] = {}) => {
return render(
MainSidebarVersionControl,
{
pinia,
i18n: i18nInstance,
...renderOptions,
},
(vue) => {
vue.use(PiniaVuePlugin);
},
);
};
describe('MainSidebarVersionControl', () => {
beforeEach(() => {
pinia = createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: merge({}, SETTINGS_STORE_DEFAULT_STATE.settings),
},
},
});
versionControlStore = useVersionControlStore();
usersStore = useUsersStore();
});
it('should render empty content', async () => {
const { getByTestId } = renderComponent({ props: { isCollapsed: false } });
expect(getByTestId('main-sidebar-version-control')).toBeInTheDocument();
expect(getByTestId('main-sidebar-version-control')).toBeEmptyDOMElement();
});
it('should render setup content', async () => {
vi.spyOn(versionControlStore, 'isEnterpriseVersionControlEnabled', 'get').mockReturnValue(true);
vi.spyOn(usersStore, 'isInstanceOwner', 'get').mockReturnValue(true);
const { getByTestId, queryByTestId } = renderComponent({ props: { isCollapsed: false } });
expect(getByTestId('main-sidebar-version-control-setup')).toBeInTheDocument();
expect(queryByTestId('main-sidebar-version-control-connected')).not.toBeInTheDocument();
});
it('should render connected content', async () => {
vi.spyOn(versionControlStore, 'preferences', 'get').mockReturnValue({
branchName: 'main',
branches: [],
authorName: '',
authorEmail: '',
repositoryUrl: '',
branchReadOnly: false,
branchColor: '#F4A6DC',
connected: true,
publicKey: '',
});
const { getByTestId, queryByTestId } = renderComponent({ props: { isCollapsed: false } });
expect(getByTestId('main-sidebar-version-control-connected')).toBeInTheDocument();
expect(queryByTestId('main-sidebar-version-control-setup')).not.toBeInTheDocument();
});
});

View file

@ -1365,6 +1365,8 @@
"settings.versionControl.sync.prompt.error": "Please enter a commit message", "settings.versionControl.sync.prompt.error": "Please enter a commit message",
"settings.versionControl.button.push": "Push", "settings.versionControl.button.push": "Push",
"settings.versionControl.button.pull": "Pull", "settings.versionControl.button.pull": "Pull",
"settings.versionControl.button.setup": "Set up version control",
"settings.versionControl.button.setup.tooltip": "You have version control enabled. Go to the settings page to connect to your Git repository.",
"settings.versionControl.modals.push.title": "Commit and push changes", "settings.versionControl.modals.push.title": "Commit and push changes",
"settings.versionControl.modals.push.description": "Select the files you want to stage in your commit and add a commit message. ", "settings.versionControl.modals.push.description": "Select the files you want to stage in your commit and add a commit message. ",
"settings.versionControl.modals.push.description.workflows": "Since you are on the Workflows page, the modified workflow files have been pre-selected for you.", "settings.versionControl.modals.push.description.workflows": "Since you are on the Workflows page, the modified workflow files have been pre-selected for you.",