fix(editor): Adding branch color (#6380)

* fix(editor): Adding branch color

* fix(editor): backend refactor preferences update

* fix(editor): frontend refactor preferences update
This commit is contained in:
Csaba Tuncsik 2023-06-06 11:23:53 +02:00 committed by GitHub
parent e95e8de500
commit dba3f44bc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 75 additions and 112 deletions

View file

@ -44,12 +44,12 @@ export class VersionControlPreferences {
): VersionControlPreferences { ): VersionControlPreferences {
return new VersionControlPreferences({ return new VersionControlPreferences({
connected: preferences.connected ?? defaultPreferences.connected, connected: preferences.connected ?? defaultPreferences.connected,
authorEmail: preferences.authorEmail ?? defaultPreferences.authorEmail,
authorName: preferences.authorName ?? defaultPreferences.authorName,
branchName: preferences.branchName ?? defaultPreferences.branchName,
branchColor: preferences.branchColor ?? defaultPreferences.branchColor,
branchReadOnly: preferences.branchReadOnly ?? defaultPreferences.branchReadOnly,
repositoryUrl: preferences.repositoryUrl ?? defaultPreferences.repositoryUrl, repositoryUrl: preferences.repositoryUrl ?? defaultPreferences.repositoryUrl,
authorName: preferences.authorName ?? defaultPreferences.authorName,
authorEmail: preferences.authorEmail ?? defaultPreferences.authorEmail,
branchName: preferences.branchName ?? defaultPreferences.branchName,
branchReadOnly: preferences.branchReadOnly ?? defaultPreferences.branchReadOnly,
branchColor: preferences.branchColor ?? defaultPreferences.branchColor,
}); });
} }
} }

View file

@ -1,4 +1,4 @@
import { Authorized, Get, Post, RestController } from '@/decorators'; import { Authorized, Get, Post, Patch, RestController } from '@/decorators';
import { import {
versionControlLicensedMiddleware, versionControlLicensedMiddleware,
versionControlLicensedAndEnabledMiddleware, versionControlLicensedAndEnabledMiddleware,
@ -83,10 +83,38 @@ export class VersionControlController {
} }
@Authorized(['global', 'owner']) @Authorized(['global', 'owner'])
@Post('/set-read-only', { middlewares: [versionControlLicensedMiddleware] }) @Patch('/preferences', { middlewares: [versionControlLicensedMiddleware] })
async setReadOnly(req: VersionControlRequest.SetReadOnly) { async updatePreferences(req: VersionControlRequest.UpdatePreferences) {
try { try {
this.versionControlPreferencesService.setBranchReadOnly(req.body.branchReadOnly); const sanitizedPreferences: Partial<VersionControlPreferences> = {
...req.body,
initRepo: false,
connected: undefined,
publicKey: undefined,
repositoryUrl: undefined,
authorName: undefined,
authorEmail: undefined,
};
const currentPreferences = this.versionControlPreferencesService.getPreferences();
await this.versionControlPreferencesService.validateVersionControlPreferences(
sanitizedPreferences,
);
if (
sanitizedPreferences.branchName &&
sanitizedPreferences.branchName !== currentPreferences.branchName
) {
await this.versionControlService.setBranch(sanitizedPreferences.branchName);
}
if (sanitizedPreferences.branchColor || sanitizedPreferences.branchReadOnly !== undefined) {
await this.versionControlPreferencesService.setPreferences(
{
branchColor: sanitizedPreferences.branchColor,
branchReadOnly: sanitizedPreferences.branchReadOnly,
},
true,
);
}
await this.versionControlService.init();
return this.versionControlPreferencesService.getPreferences(); return this.versionControlPreferencesService.getPreferences();
} catch (error) { } catch (error) {
throw new BadRequestError((error as { message: string }).message); throw new BadRequestError((error as { message: string }).message);
@ -113,16 +141,6 @@ export class VersionControlController {
} }
} }
@Authorized(['global', 'owner'])
@Post('/set-branch', { middlewares: [versionControlLicensedMiddleware] })
async setBranch(req: VersionControlRequest.SetBranch) {
try {
return await this.versionControlService.setBranch(req.body.branch);
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
@Authorized(['global', 'owner']) @Authorized(['global', 'owner'])
@Post('/push-workfolder', { middlewares: [versionControlLicensedAndEnabledMiddleware] }) @Post('/push-workfolder', { middlewares: [versionControlLicensedAndEnabledMiddleware] })
async pushWorkfolder( async pushWorkfolder(

View file

@ -172,7 +172,7 @@ export class VersionControlService {
async setBranch(branch: string): Promise<{ branches: string[]; currentBranch: string }> { async setBranch(branch: string): Promise<{ branches: string[]; currentBranch: string }> {
await this.versionControlPreferencesService.setPreferences({ await this.versionControlPreferencesService.setPreferences({
branchName: branch, branchName: branch,
connected: true, connected: branch?.length > 0,
}); });
return this.gitService.setBranch(branch); return this.gitService.setBranch(branch);
} }
@ -216,40 +216,6 @@ export class VersionControlService {
}); });
} }
// async pushWorkfolder(
// options: VersionControlPushWorkFolder,
// ): Promise<PushResult | VersionControlledFile[]> {
// await this.gitService.fetch();
// await this.export(); // refresh workfolder
// await this.stage(options);
// await this.gitService.commit(options.message ?? 'Updated Workfolder');
// return this.gitService.push({
// branch: this.versionControlPreferencesService.getBranchName(),
// force: options.force ?? false,
// });
// }
// TODO: Alternate implementation for pull
// async pullWorkfolder(
// options: VersionControllPullOptions,
// ): Promise<ImportResult | VersionControlledFile[] | PullResult | undefined> {
// const diffResult = await this.getStatus();
// const possibleConflicts = diffResult?.filter((file) => file.conflict);
// if (possibleConflicts?.length > 0 || options.force === true) {
// await this.unstage();
// if (options.force === true) {
// return this.resetWorkfolder(options);
// } else {
// return diffResult;
// }
// }
// const pullResult = await this.gitService.pull();
// if (options.importAfterPull) {
// return this.import(options);
// }
// return pullResult;
// }
async pullWorkfolder( async pullWorkfolder(
options: VersionControllPullOptions, options: VersionControllPullOptions,
): Promise<ImportResult | StatusResult | undefined> { ): Promise<ImportResult | StatusResult | undefined> {

View file

@ -114,19 +114,10 @@ export class VersionControlPreferencesService {
return this.versionControlPreferences; return this.versionControlPreferences;
} }
setBranchReadOnly(branchReadOnly: boolean): void {
this._versionControlPreferences.branchReadOnly = branchReadOnly;
}
async validateVersionControlPreferences( async validateVersionControlPreferences(
preferences: Partial<VersionControlPreferences>, preferences: Partial<VersionControlPreferences>,
allowMissingProperties = true, allowMissingProperties = true,
): Promise<ValidationError[]> { ): Promise<ValidationError[]> {
if (this.isVersionControlConnected()) {
if (preferences.repositoryUrl !== this._versionControlPreferences.repositoryUrl) {
throw new Error('Cannot change repository while connected');
}
}
const preferencesObject = new VersionControlPreferences(preferences); const preferencesObject = new VersionControlPreferences(preferences);
const validationResult = await validate(preferencesObject, { const validationResult = await validate(preferencesObject, {
forbidUnknownValues: false, forbidUnknownValues: false,

View file

@ -9,6 +9,14 @@ import type { IDataObject } from 'n8n-workflow';
const versionControlApiRoot = '/version-control'; const versionControlApiRoot = '/version-control';
const createPreferencesRequestFn =
(method: 'POST' | 'PATCH') =>
async (
context: IRestApiContext,
preferences: Partial<VersionControlPreferences>,
): Promise<VersionControlPreferences> =>
makeRestApiRequest(context, method, `${versionControlApiRoot}/preferences`, preferences);
export const pushWorkfolder = async ( export const pushWorkfolder = async (
context: IRestApiContext, context: IRestApiContext,
data: IDataObject, data: IDataObject,
@ -29,19 +37,8 @@ export const getBranches = async (
return makeRestApiRequest(context, 'GET', `${versionControlApiRoot}/get-branches`); return makeRestApiRequest(context, 'GET', `${versionControlApiRoot}/get-branches`);
}; };
export const setBranch = async ( export const savePreferences = createPreferencesRequestFn('POST');
context: IRestApiContext, export const updatePreferences = createPreferencesRequestFn('PATCH');
branch: string,
): Promise<{ branches: string[]; currentBranch: string }> => {
return makeRestApiRequest(context, 'POST', `${versionControlApiRoot}/set-branch`, { branch });
};
export const setPreferences = async (
context: IRestApiContext,
preferences: Partial<VersionControlPreferences>,
): Promise<VersionControlPreferences> => {
return makeRestApiRequest(context, 'POST', `${versionControlApiRoot}/preferences`, preferences);
};
export const getPreferences = async ( export const getPreferences = async (
context: IRestApiContext, context: IRestApiContext,
@ -68,15 +65,6 @@ export const disconnect = async (
}); });
}; };
export const setBranchReadonly = async (
context: IRestApiContext,
branchReadOnly: boolean,
): Promise<string> => {
return makeRestApiRequest(context, 'POST', `${versionControlApiRoot}/set-read-only`, {
branchReadOnly,
});
};
export const generateKeyPair = async (context: IRestApiContext): Promise<string> => { export const generateKeyPair = async (context: IRestApiContext): Promise<string> => {
return makeRestApiRequest(context, 'POST', `${versionControlApiRoot}/generate-key-pair`); return makeRestApiRequest(context, 'POST', `${versionControlApiRoot}/generate-key-pair`);
}; };

View file

@ -71,7 +71,10 @@ async function pullWorkfolder() {
</script> </script>
<template> <template>
<div :class="{ [$style.sync]: true, [$style.collapsed]: isCollapsed }"> <div
:class="{ [$style.sync]: true, [$style.collapsed]: isCollapsed }"
:style="{ borderLeftColor: versionControlStore.preferences.branchColor }"
>
<span> <span>
<n8n-icon icon="code-branch" /> <n8n-icon icon="code-branch" />
{{ currentBranch }} {{ currentBranch }}
@ -128,10 +131,11 @@ async function pullWorkfolder() {
<style lang="scss" module> <style lang="scss" module>
.sync { .sync {
padding: var(--spacing-s) var(--spacing-s) var(--spacing-s) var(--spacing-l); padding: var(--spacing-s) var(--spacing-s) var(--spacing-s) var(--spacing-m);
margin: 0 calc(var(--spacing-l) * -1) calc(var(--spacing-m) * -1); margin: 0 calc(var(--spacing-l) * -1) calc(var(--spacing-m) * -1);
background: var(--color-background-light); background: var(--color-background-light);
border-top: 1px solid var(--color-foreground-light); 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);
span { span {
@ -145,7 +149,7 @@ async function pullWorkfolder() {
.collapsed { .collapsed {
text-align: center; text-align: center;
margin-left: calc(var(--spacing-xl) * -1); padding-left: var(--spacing-xs);
> span { > span {
display: none; display: none;

View file

@ -48,6 +48,13 @@ export const useVersionControlStore = defineStore('versionControl', () => {
Object.assign(preferences, data); Object.assign(preferences, data);
}; };
const makePreferencesAction =
(action: typeof vcApi.savePreferences | typeof vcApi.updatePreferences) =>
async (preferences: Partial<VersionControlPreferences>) => {
const data = await action(rootStore.getRestApiContext, preferences);
setPreferences(data);
};
const getBranches = async () => { const getBranches = async () => {
const data = await vcApi.getBranches(rootStore.getRestApiContext); const data = await vcApi.getBranches(rootStore.getRestApiContext);
setPreferences(data); setPreferences(data);
@ -58,15 +65,9 @@ export const useVersionControlStore = defineStore('versionControl', () => {
setPreferences(data); setPreferences(data);
}; };
const savePreferences = async (preferences: Partial<VersionControlPreferences>) => { const savePreferences = makePreferencesAction(vcApi.savePreferences);
const data = await vcApi.setPreferences(rootStore.getRestApiContext, preferences);
setPreferences(data);
};
const setBranch = async (branch: string) => { const updatePreferences = makePreferencesAction(vcApi.updatePreferences);
const data = await vcApi.setBranch(rootStore.getRestApiContext, branch);
setPreferences({ ...data, connected: true });
};
const disconnect = async (keepKeyPair: boolean) => { const disconnect = async (keepKeyPair: boolean) => {
await vcApi.disconnect(rootStore.getRestApiContext, keepKeyPair); await vcApi.disconnect(rootStore.getRestApiContext, keepKeyPair);
@ -90,10 +91,6 @@ export const useVersionControlStore = defineStore('versionControl', () => {
return vcApi.getAggregatedStatus(rootStore.getRestApiContext); return vcApi.getAggregatedStatus(rootStore.getRestApiContext);
}; };
const setBranchReadonly = async (branchReadOnly: boolean) => {
return vcApi.setBranchReadonly(rootStore.getRestApiContext, branchReadOnly);
};
return { return {
isEnterpriseVersionControlEnabled, isEnterpriseVersionControlEnabled,
state, state,
@ -105,10 +102,9 @@ export const useVersionControlStore = defineStore('versionControl', () => {
generateKeyPair, generateKeyPair,
getBranches, getBranches,
savePreferences, savePreferences,
setBranch, updatePreferences,
disconnect, disconnect,
getStatus, getStatus,
getAggregatedStatus, getAggregatedStatus,
setBranchReadonly,
}; };
}); });

View file

@ -2,8 +2,7 @@
import { computed, reactive, onBeforeMount, ref } from 'vue'; import { computed, reactive, onBeforeMount, ref } from 'vue';
import type { Rule, RuleGroup } from 'n8n-design-system/types'; import type { Rule, RuleGroup } from 'n8n-design-system/types';
import { MODAL_CONFIRM, VALID_EMAIL_REGEX } from '@/constants'; import { MODAL_CONFIRM, VALID_EMAIL_REGEX } from '@/constants';
import { useVersionControlStore } from '@/stores/versionControl.store'; import { useUIStore, useVersionControlStore } from '@/stores';
import { useUIStore } from '@/stores/ui.store';
import { useToast, useMessage, useLoadingService, useI18n } from '@/composables'; import { useToast, useMessage, useLoadingService, useI18n } from '@/composables';
import CopyInput from '@/components/CopyInput.vue'; import CopyInput from '@/components/CopyInput.vue';
@ -67,10 +66,11 @@ const onDisconnect = async () => {
const onSave = async () => { const onSave = async () => {
loadingService.startLoading(); loadingService.startLoading();
try { try {
await Promise.all([ await versionControlStore.updatePreferences({
versionControlStore.setBranch(versionControlStore.preferences.branchName), branchName: versionControlStore.preferences.branchName,
versionControlStore.setBranchReadonly(versionControlStore.preferences.branchReadOnly), branchReadOnly: versionControlStore.preferences.branchReadOnly,
]); branchColor: versionControlStore.preferences.branchColor,
});
toast.showMessage({ toast.showMessage({
title: locale.baseText('settings.versionControl.saved.title'), title: locale.baseText('settings.versionControl.saved.title'),
type: 'success', type: 'success',
@ -92,7 +92,7 @@ const goToUpgrade = () => {
uiStore.goToUpgrade('version-control', 'upgrade-version-control'); uiStore.goToUpgrade('version-control', 'upgrade-version-control');
}; };
onBeforeMount(async () => { onBeforeMount(() => {
if (versionControlStore.preferences.connected) { if (versionControlStore.preferences.connected) {
isConnected.value = true; isConnected.value = true;
void versionControlStore.getBranches(); void versionControlStore.getBranches();
@ -318,12 +318,12 @@ async function refreshSshKey() {
</i18n> </i18n>
</n8n-checkbox> </n8n-checkbox>
</div> </div>
<!-- <div :class="$style.group"> <div :class="$style.group">
<label>{{ locale.baseText('settings.versionControl.color') }}</label> <label>{{ locale.baseText('settings.versionControl.color') }}</label>
<div> <div>
<n8n-color-picker size="small" v-model="versionControlStore.preferences.branchColor" /> <n8n-color-picker size="small" v-model="versionControlStore.preferences.branchColor" />
</div> </div>
</div> --> </div>
<div :class="[$style.group, 'pt-s']"> <div :class="[$style.group, 'pt-s']">
<n8n-button <n8n-button
@click="onSave" @click="onSave"