mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-26 13:14:07 -08:00
fc7aa8bd66
* initial telemetry setup and adjusted pull return * quicksave before merge * feat: add conflicting workflow list to pull modal * feat: update source control pull modal * fix: fix linting issue * feat: add Enter keydown event for submitting source control push modal (no-changelog) feat: add Enter keydown event for submitting source control push modal * quicksave * user workflow table for export * improve telemetry data * pull api telemetry * fix lint * Copy tweaks. * remove authorName and authorEmail and pick from user * rename owners.json to workflow_owners.json * ignore credential conflicts on pull * feat: several push/pull flow changes and design update * pull and push return same data format * fix: add One last step toast for successful pull * feat: add up to date pull toast * fix: add proper Learn more link for push and pull modals * do not await tracking being sent * fix import * fix await * add more sourcecontrolfile status * Minor copy tweak for "More info". * Minor copy tweak for "More info". * ignore variable_stub conflicts on pull * ignore whitespace differences * do not show remote workflows that are not yet created * fix telemetry * fix toast when pulling deleted wf * lint fix * refactor and make some imports dynamic * fix variable edit validation * fix telemetry response * improve telemetry * fix unintenional delete commit * fix status unknown issue * fix up to date toast * do not export active state and reapply versionid * use update instead of upsert * fix: show all workflows when clicking push to git * feat: update Up to date pull translation * fix: update read only env checks * do not update versionid of only active flag changes * feat: prevent access to new workflow and templates import when read only env * feat: send only active state and version if workflow state is not dirty * fix: Detect when only active state has changed and prevent generation a new version ID * feat: improve readonly env messages * make getPreferences public * fix telemetry issue * fix: add partial workflow update based on dirty state when changing active state * update unit tests * fix: remove unsaved changes check in readOnlyEnv * fix: disable push to git button when read onyl env * fix: update readonly toast duration * fix: fix pinning and title input in protected mode * initial commit (NOT working) * working push * cleanup and implement pull * fix getstatus * update import to new method * var and tag diffs are no conflicts * only show pull conflict for workflows * refactor and ignore faulty credentials * add sanitycheck for missing git folder * prefer fetch over pull and limit depth to 1 * back to pull... * fix setting branch on initial connect * fix test * remove clean workfolder * refactor: Remove some unnecessary code * Fixed links to docs. * fix getstatus query params * lint fix * dialog to show local and remote name on conflict * only show remote name on conflict * fix credential expression export * fix: Broken test * dont show toast on pull with empty var/tags and refactor * apply frontend changes from old branch * fix tag with same name import * fix buttons shown for non instance owners * prepare local storage key for removal * refactor: Change wording on pushing and pulling * refactor: Change menu item * test: Fix broken test * Update packages/cli/src/environments/sourceControl/types/sourceControlPushWorkFolder.ts Co-authored-by: Iván Ovejero <ivov.src@gmail.com> --------- Co-authored-by: Alex Grozav <alex@grozav.com> Co-authored-by: Giulio Andreini <g.andreini@gmail.com> Co-authored-by: Omar Ajoue <krynble@gmail.com> Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
257 lines
7 KiB
TypeScript
257 lines
7 KiB
TypeScript
import Container from 'typedi';
|
|
import {
|
|
generateSshKeyPair,
|
|
getRepoType,
|
|
getTrackingInformationFromPostPushResult,
|
|
getTrackingInformationFromPrePushResult,
|
|
getTrackingInformationFromPullResult,
|
|
sourceControlFoldersExistCheck,
|
|
} from '@/environments/sourceControl/sourceControlHelper.ee';
|
|
import { License } from '@/License';
|
|
import { SourceControlPreferencesService } from '@/environments/sourceControl/sourceControlPreferences.service.ee';
|
|
import { UserSettings } from 'n8n-core';
|
|
import path from 'path';
|
|
import {
|
|
SOURCE_CONTROL_SSH_FOLDER,
|
|
SOURCE_CONTROL_GIT_FOLDER,
|
|
SOURCE_CONTROL_SSH_KEY_NAME,
|
|
} from '@/environments/sourceControl/constants';
|
|
import { LoggerProxy } from 'n8n-workflow';
|
|
import { getLogger } from '@/Logger';
|
|
import { constants as fsConstants, accessSync } from 'fs';
|
|
import type { SourceControlledFile } from '@/environments/sourceControl/types/sourceControlledFile';
|
|
|
|
const pushResult: SourceControlledFile[] = [
|
|
{
|
|
file: 'credential_stubs/kkookWGIeey9K4Kt.json',
|
|
id: 'kkookWGIeey9K4Kt',
|
|
name: '(deleted)',
|
|
type: 'credential',
|
|
status: 'deleted',
|
|
location: 'local',
|
|
conflict: false,
|
|
updatedAt: '',
|
|
pushed: true,
|
|
},
|
|
{
|
|
file: 'variable_stubs.json',
|
|
id: 'variables',
|
|
name: 'variables',
|
|
type: 'variables',
|
|
status: 'modified',
|
|
location: 'local',
|
|
conflict: false,
|
|
updatedAt: '',
|
|
pushed: true,
|
|
},
|
|
{
|
|
file: 'workflows/BpFS26gViuGqrIVP.json',
|
|
id: 'BpFS26gViuGqrIVP',
|
|
name: 'My workflow 5',
|
|
type: 'workflow',
|
|
status: 'modified',
|
|
location: 'remote',
|
|
conflict: true,
|
|
pushed: true,
|
|
updatedAt: '2023-07-10T10:10:59.000Z',
|
|
},
|
|
{
|
|
file: 'workflows/BpFS26gViuGqrIVP.json',
|
|
id: 'BpFS26gViuGqrIVP',
|
|
name: 'My workflow 5',
|
|
type: 'workflow',
|
|
status: 'modified',
|
|
location: 'local',
|
|
conflict: true,
|
|
updatedAt: '2023-07-10T10:10:59.000Z',
|
|
},
|
|
{
|
|
file: 'workflows/dAU6dNthm4TR3gXx.json',
|
|
id: 'dAU6dNthm4TR3gXx',
|
|
name: 'My workflow 7',
|
|
type: 'workflow',
|
|
status: 'created',
|
|
location: 'local',
|
|
conflict: false,
|
|
pushed: true,
|
|
updatedAt: '2023-07-10T10:02:45.186Z',
|
|
},
|
|
{
|
|
file: 'workflows/haQetoXq9GxHSkft.json',
|
|
id: 'haQetoXq9GxHSkft',
|
|
name: 'My workflow 6',
|
|
type: 'workflow',
|
|
status: 'created',
|
|
location: 'local',
|
|
conflict: false,
|
|
updatedAt: '2023-07-10T10:02:39.276Z',
|
|
},
|
|
];
|
|
|
|
const pullResult: SourceControlledFile[] = [
|
|
{
|
|
file: 'credential_stubs/kkookWGIeey9K4Kt.json',
|
|
id: 'kkookWGIeey9K4Kt',
|
|
name: '(deleted)',
|
|
type: 'credential',
|
|
status: 'deleted',
|
|
location: 'local',
|
|
conflict: false,
|
|
updatedAt: '',
|
|
},
|
|
{
|
|
file: 'credential_stubs/abcdeWGIeey9K4aa.json',
|
|
id: 'abcdeWGIeey9K4aa',
|
|
name: 'modfied credential',
|
|
type: 'credential',
|
|
status: 'modified',
|
|
location: 'local',
|
|
conflict: false,
|
|
updatedAt: '',
|
|
},
|
|
{
|
|
file: 'workflows/BpFS26gViuGqrIVP.json',
|
|
id: 'BpFS26gViuGqrIVP',
|
|
name: '(deleted)',
|
|
type: 'workflow',
|
|
status: 'deleted',
|
|
location: 'local',
|
|
conflict: false,
|
|
updatedAt: '',
|
|
},
|
|
{
|
|
file: 'variable_stubs.json',
|
|
id: 'variables',
|
|
name: 'variables',
|
|
type: 'variables',
|
|
status: 'modified',
|
|
location: 'local',
|
|
conflict: false,
|
|
updatedAt: '',
|
|
},
|
|
{
|
|
file: 'workflows/dAU6dNthm4TR3gXx.json',
|
|
id: 'dAU6dNthm4TR3gXx',
|
|
name: 'My workflow 7',
|
|
type: 'workflow',
|
|
status: 'created',
|
|
location: 'local',
|
|
conflict: false,
|
|
updatedAt: '2023-07-10T10:02:45.186Z',
|
|
},
|
|
{
|
|
file: 'workflows/haQetoXq9GxHSkft.json',
|
|
id: 'haQetoXq9GxHSkft',
|
|
name: 'My workflow 6',
|
|
type: 'workflow',
|
|
status: 'modified',
|
|
location: 'local',
|
|
conflict: false,
|
|
updatedAt: '2023-07-10T10:02:39.276Z',
|
|
},
|
|
];
|
|
|
|
beforeAll(async () => {
|
|
LoggerProxy.init(getLogger());
|
|
Container.get(License).isSourceControlLicensed = () => true;
|
|
Container.get(SourceControlPreferencesService).getPreferences = () => ({
|
|
branchName: 'main',
|
|
connected: true,
|
|
repositoryUrl: 'git@example.com:n8ntest/n8n_testrepo.git',
|
|
branchReadOnly: false,
|
|
branchColor: '#5296D6',
|
|
publicKey:
|
|
'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDBSz2nMZAiUBWe6n89aWd5x9QMcIOaznVW3fpuCYC4L n8n deploy key',
|
|
});
|
|
});
|
|
|
|
describe('Source Control', () => {
|
|
it('should generate an SSH key pair', async () => {
|
|
const keyPair = await generateSshKeyPair();
|
|
expect(keyPair.privateKey).toBeTruthy();
|
|
expect(keyPair.privateKey).toContain('BEGIN OPENSSH PRIVATE KEY');
|
|
expect(keyPair.publicKey).toBeTruthy();
|
|
expect(keyPair.publicKey).toContain('ssh-ed25519');
|
|
});
|
|
|
|
it('should check for git and ssh folders and create them if required', async () => {
|
|
const userFolder = UserSettings.getUserN8nFolderPath();
|
|
const sshFolder = path.join(userFolder, SOURCE_CONTROL_SSH_FOLDER);
|
|
const gitFolder = path.join(userFolder, SOURCE_CONTROL_GIT_FOLDER);
|
|
const sshKeyName = path.join(sshFolder, SOURCE_CONTROL_SSH_KEY_NAME);
|
|
let hasThrown = false;
|
|
try {
|
|
accessSync(sshFolder, fsConstants.F_OK);
|
|
} catch (error) {
|
|
hasThrown = true;
|
|
}
|
|
expect(hasThrown).toBeTruthy();
|
|
hasThrown = false;
|
|
try {
|
|
accessSync(gitFolder, fsConstants.F_OK);
|
|
} catch (error) {
|
|
hasThrown = true;
|
|
}
|
|
expect(hasThrown).toBeTruthy();
|
|
// create missing folders
|
|
expect(sourceControlFoldersExistCheck([gitFolder, sshFolder], true)).toBe(false);
|
|
// find folders this time
|
|
expect(sourceControlFoldersExistCheck([gitFolder, sshFolder], true)).toBe(true);
|
|
expect(accessSync(sshFolder, fsConstants.F_OK)).toBeUndefined();
|
|
expect(accessSync(gitFolder, fsConstants.F_OK)).toBeUndefined();
|
|
});
|
|
|
|
it('should check if source control is licensed', async () => {
|
|
expect(Container.get(License).isSourceControlLicensed()).toBe(true);
|
|
});
|
|
|
|
it('should get repo type from url', async () => {
|
|
expect(getRepoType('git@github.com:n8ntest/n8n_testrepo.git')).toBe('github');
|
|
expect(getRepoType('git@gitlab.com:n8ntest/n8n_testrepo.git')).toBe('gitlab');
|
|
expect(getRepoType('git@mygitea.io:n8ntest/n8n_testrepo.git')).toBe('other');
|
|
});
|
|
|
|
it('should get tracking information from pre-push results', () => {
|
|
const trackingResult = getTrackingInformationFromPrePushResult(pushResult);
|
|
expect(trackingResult).toEqual({
|
|
workflows_eligible: 3,
|
|
workflows_eligible_with_conflicts: 1,
|
|
creds_eligible: 1,
|
|
creds_eligible_with_conflicts: 0,
|
|
variables_eligible: 1,
|
|
});
|
|
});
|
|
|
|
it('should get tracking information from post-push results', () => {
|
|
const trackingResult = getTrackingInformationFromPostPushResult(pushResult);
|
|
expect(trackingResult).toEqual({
|
|
workflows_pushed: 2,
|
|
workflows_eligible: 3,
|
|
creds_pushed: 1,
|
|
variables_pushed: 1,
|
|
});
|
|
});
|
|
|
|
it('should get tracking information from pull results', () => {
|
|
const trackingResult = getTrackingInformationFromPullResult(pullResult);
|
|
expect(trackingResult).toEqual({
|
|
cred_conflicts: 1,
|
|
workflow_conflicts: 1,
|
|
workflow_updates: 3,
|
|
});
|
|
});
|
|
|
|
it('should class validate correct preferences', async () => {
|
|
const validPreferences = {
|
|
branchName: 'main',
|
|
repositoryUrl: 'git@example.com:n8ntest/n8n_testrepo.git',
|
|
branchReadOnly: false,
|
|
branchColor: '#5296D6',
|
|
};
|
|
const validationResult = await Container.get(
|
|
SourceControlPreferencesService,
|
|
).validateSourceControlPreferences(validPreferences);
|
|
expect(validationResult).toBeTruthy();
|
|
});
|
|
});
|