Skip to content

Commit

Permalink
Merge branch 'master' into ADO-32-e2e-undo
Browse files Browse the repository at this point in the history
* master:
  feat: Add sharing permissions info for workflow sharees (#4892)
  fix(editor): Fix credential sharing issues handler when no matching id or name (#4879)
  fix: Update translation for sharing toast (no-changelog) (#4893)
  fix: Remove delete sharee prompt except when deleting last cred accessor (no-changelog) (#4894)
  fix: Remove foreign credentials when copying nodes or duplicating workflow (#4880)
  feat: Update credential test error message for sharees (#4864)
  feat: Handle sharing features when user skips owner setup (#4850)
  fix(editor): Schema view shows checkbox in case of empty data (#4889)
  fix(core): Remove `nodeGetter` checks (#4883)
  • Loading branch information
MiloradFilipovic committed Dec 12, 2022
2 parents 21fc9ec + c013245 commit a29dd6e
Show file tree
Hide file tree
Showing 19 changed files with 225 additions and 115 deletions.
2 changes: 1 addition & 1 deletion packages/editor-ui/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export interface IWorkflowDb {
sharedWith?: Array<Partial<IUser>>;
ownedBy?: Partial<IUser>;
versionId: string;
usedCredentials?: Array<Partial<ICredentialsResponse>>;
usedCredentials?: IUsedCredential[];
}

// Identical to cli.Interfaces.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<banner
v-if="authError && !showValidationWarning"
theme="danger"
:message="$locale.baseText('credentialEdit.credentialConfig.couldntConnectWithTheseSettings')"
:message="$locale.baseText(`credentialEdit.credentialConfig.couldntConnectWithTheseSettings${!credentialPermissions.isOwner ? '.sharee' : ''}`, { interpolate: { owner: credentialOwnerName } })"
:details="authError"
:buttonLabel="$locale.baseText('credentialEdit.credentialConfig.retry')"
buttonLoadingLabel="Retrying"
Expand Down Expand Up @@ -204,7 +204,7 @@ export default mixins(restApi).extend({
return (this.credentialType as ICredentialType).name;
},
credentialOwnerName(): string {
return this.credentialsStore.getCredentialOwnerName(this.credentialId);
return this.credentialsStore.getCredentialOwnerName(`${this.credentialId}`);
},
documentationUrl(): string {
const type = this.credentialType as ICredentialType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
:credentialData="credentialData"
:credentialId="credentialId"
:credentialPermissions="credentialPermissions"
:modalBus="modalBus"
@change="onChangeSharedWith"
/>
</enterprise-edition>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
<template>
<div :class="$style.container">
<n8n-info-tip :bold="false" class="mb-s">
<template v-if="credentialPermissions.isOwner">
{{ $locale.baseText('credentialEdit.credentialSharing.info.owner') }}
</template>
<template v-else>
{{ $locale.baseText('credentialEdit.credentialSharing.info.sharee', { interpolate: { credentialOwnerName } }) }}
</template>
</n8n-info-tip>
<n8n-info-tip :bold="false" v-if="!credentialPermissions.isOwner && credentialPermissions.isInstanceOwner">
{{ $locale.baseText('credentialEdit.credentialSharing.info.instanceOwner') }}
</n8n-info-tip>
<n8n-user-select
v-if="credentialPermissions.updateSharing"
size="large"
:users="usersList"
:currentUserId="usersStore.currentUser.id"
:placeholder="$locale.baseText('credentialEdit.credentialSharing.select.placeholder')"
@input="onAddSharee"
>
<template #prefix>
<n8n-icon icon="search" />
</template>
</n8n-user-select>
<n8n-users-list
:users="sharedWithList"
:currentUserId="usersStore.currentUser.id"
:delete-label="$locale.baseText('credentialEdit.credentialSharing.list.delete')"
:readonly="!credentialPermissions.updateSharing"
@delete="onRemoveSharee"
/>
<div v-if="isDefaultUser">
<n8n-action-box
:description="$locale.baseText('credentialEdit.credentialSharing.isDefaultUser.description')"
:buttonText="$locale.baseText('credentialEdit.credentialSharing.isDefaultUser.button')"
@click="goToUsersSettings"
/>
</div>
<div v-else>
<n8n-info-tip :bold="false" class="mb-s">
<template v-if="credentialPermissions.isOwner">
{{ $locale.baseText('credentialEdit.credentialSharing.info.owner') }}
</template>
<template v-else>
{{ $locale.baseText('credentialEdit.credentialSharing.info.sharee', { interpolate: { credentialOwnerName } }) }}
</template>
</n8n-info-tip>
<n8n-info-tip :bold="false" v-if="!credentialPermissions.isOwner && credentialPermissions.isInstanceOwner">
{{ $locale.baseText('credentialEdit.credentialSharing.info.instanceOwner') }}
</n8n-info-tip>
<n8n-user-select
v-if="credentialPermissions.updateSharing"
size="large"
:users="usersList"
:currentUserId="usersStore.currentUser.id"
:placeholder="$locale.baseText('credentialEdit.credentialSharing.select.placeholder')"
@input="onAddSharee"
>
<template #prefix>
<n8n-icon icon="search" />
</template>
</n8n-user-select>
<n8n-users-list
:users="sharedWithList"
:currentUserId="usersStore.currentUser.id"
:delete-label="$locale.baseText('credentialEdit.credentialSharing.list.delete')"
:readonly="!credentialPermissions.updateSharing"
@delete="onRemoveSharee"
/>
</div>
</div>
</template>

Expand All @@ -40,17 +49,21 @@ import {showMessage} from "@/mixins/showMessage";
import { mapStores } from 'pinia';
import { useUsersStore } from '@/stores/users';
import { useCredentialsStore } from "@/stores/credentials";
import {VIEWS} from "@/constants";
export default mixins(
showMessage,
).extend({
name: 'CredentialSharing',
props: ['credential', 'credentialId', 'credentialData', 'sharedWith', 'credentialPermissions'],
props: ['credential', 'credentialId', 'credentialData', 'sharedWith', 'credentialPermissions', 'modalBus'],
computed: {
...mapStores(
useCredentialsStore,
useUsersStore,
),
isDefaultUser(): boolean {
return this.usersStore.isDefaultUser;
},
usersList(): IUser[] {
return this.usersStore.allUsers.filter((user: IUser) => {
const isCurrentUser = user.id === this.usersStore.currentUser?.id;
Expand All @@ -68,7 +81,7 @@ export default mixins(
].concat(this.credentialData.sharedWith || []);
},
credentialOwnerName(): string {
return this.credentialsStore.getCredentialOwnerName(this.credentialId);
return this.credentialsStore.getCredentialOwnerName(`${this.credentialId}`);
},
},
methods: {
Expand Down Expand Up @@ -98,6 +111,10 @@ export default mixins(
async loadUsers() {
await this.usersStore.fetchUsers();
},
goToUsersSettings() {
this.$router.push({ name: VIEWS.USERS_SETTINGS });
this.modalBus.$emit('close');
},
},
mounted() {
this.loadUsers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,10 @@ export default mixins(showMessage, workflowHelpers, restApi).extend({
try {
let workflowToUpdate: IWorkflowDataUpdate | undefined;
if (currentWorkflowId !== PLACEHOLDER_EMPTY_WORKFLOW_ID) {
const { createdAt, updatedAt, ...workflow } = await this.restApi().getWorkflow(this.data.id);
const { createdAt, updatedAt, usedCredentials, ...workflow } = await this.restApi().getWorkflow(this.data.id);
workflowToUpdate = workflow;
this.removeForeignCredentialsFromWorkflow(workflowToUpdate, this.credentialsStore.allCredentials);
}
const saved = await this.saveAsNewWorkflow({
Expand Down
17 changes: 15 additions & 2 deletions packages/editor-ui/src/components/NodeCredentials.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
>
<div v-if="readonly || isReadOnly">
<n8n-input
:value="selected && selected[credentialTypeDescription.name] && selected[credentialTypeDescription.name].name"
:value="getSelectedName(credentialTypeDescription.name)"
disabled
size="small"
/>
Expand All @@ -26,7 +26,12 @@
v-else
:class="issues.length ? $style.hasIssues : $style.input"
>
<n8n-select :value="getSelectedId(credentialTypeDescription.name)" @change="(value) => onCredentialSelected(credentialTypeDescription.name, value)" :placeholder="$locale.baseText('nodeCredentials.selectCredential')" size="small">
<n8n-select
:value="getSelectedId(credentialTypeDescription.name)"
@change="(value) => onCredentialSelected(credentialTypeDescription.name, value)"
:placeholder="getSelectPlaceholder(credentialTypeDescription.name, issues)"
size="small"
>
<n8n-option
v-for="(item) in getCredentialOptions(credentialTypeDescription.name)"
:key="item.id"
Expand Down Expand Up @@ -180,6 +185,14 @@ export default mixins(
}
return undefined;
},
getSelectedName(type: string) {
return this.selected?.[type]?.name;
},
getSelectPlaceholder(type: string, issues: string[]) {
return issues.length && this.getSelectedName(type)
? this.$locale.baseText('nodeCredentials.selectedCredentialUnavailable', { interpolate: { name: this.getSelectedName(type) } })
: this.$locale.baseText('nodeCredentials.selectCredential');
},
credentialInputWrapperStyle (credentialType: string) {
let deductWidth = 0;
const styles = {
Expand Down
2 changes: 1 addition & 1 deletion packages/editor-ui/src/components/RunData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@
/>

<run-data-schema
v-else-if="hasNodeRun && displayMode === 'schema' && jsonData?.length > 0"
v-else-if="hasNodeRun && displayMode === 'schema'"
:data="jsonData"
:mappingEnabled="mappingEnabled"
:distanceFromActive="distanceFromActive"
Expand Down
100 changes: 55 additions & 45 deletions packages/editor-ui/src/components/RunDataSchema.test.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,69 @@
import { PiniaVuePlugin } from 'pinia';
import { createTestingPinia } from '@pinia/testing';
import { render } from '@testing-library/vue';
import { render, cleanup } from '@testing-library/vue';
import RunDataJsonSchema from '@/components/RunDataSchema.vue';
import { STORES } from "@/constants";

describe('RunDataJsonSchema.vue', () => {
it('renders json schema properly', () => {
const { container } = render(RunDataJsonSchema, {
pinia: createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: {
templates: {
enabled: true,
host: 'https://api.n8n.io/api/',
},
},
const renderOptions = {
pinia: createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: {
templates: {
enabled: true,
host: 'https://api.n8n.io/api/',
},
},
}),
stubs: ['font-awesome-icon'],
props: {
mappingEnabled: true,
distanceFromActive: 1,
runIndex: 1,
totalRuns: 2,
node: {
parameters: {
keepOnlySet: false,
values: {},
options: {},
},
id: '820ea733-d8a6-4379-8e73-88a2347ea003',
name: 'Set',
type: 'n8n-nodes-base.set',
typeVersion: 1,
position: [
380,
1060,
],
disabled: false,
},
data: [{ name: 'John', age: 22, hobbies: ['surfing', 'traveling'] }, { name: 'Joe', age: 33, hobbies: ['skateboarding', 'gaming'] }],
},
mocks: {
$locale: {
baseText() {
return '';
},
},
$store: {
getters: {},
},
},
}),
stubs: ['font-awesome-icon'],
props: {
mappingEnabled: true,
distanceFromActive: 1,
runIndex: 1,
totalRuns: 2,
node: {
parameters: {
keepOnlySet: false,
values: {},
options: {},
},
id: '820ea733-d8a6-4379-8e73-88a2347ea003',
name: 'Set',
type: 'n8n-nodes-base.set',
typeVersion: 1,
position: [
380,
1060,
],
disabled: false,
},
data: [{}],
},
mocks: {
$locale: {
baseText() {
return '';
},
},
},
};

beforeEach(cleanup);

it('renders schema for empty data', () => {
const { container } = render(RunDataJsonSchema, renderOptions,
vue => {
vue.use(PiniaVuePlugin);
});
expect(container).toMatchSnapshot();
});

it('renders schema for data', () => {
renderOptions.props.data = [{ name: 'John', age: 22, hobbies: ['surfing', 'traveling'] }, { name: 'Joe', age: 33, hobbies: ['skateboarding', 'gaming'] }];
const { container } = render(RunDataJsonSchema, renderOptions,
vue => {
vue.use(PiniaVuePlugin);
});
Expand Down
8 changes: 7 additions & 1 deletion packages/editor-ui/src/components/RunDataSchema.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useWebhooksStore } from "@/stores/webhooks";
import { runExternalHook } from "@/mixins/externalHooks";
import { telemetry } from "@/plugins/telemetry";
import { IDataObject } from "n8n-workflow";
import { getSchema, mergeDeep } from "@/utils";
import { getSchema, isEmpty, mergeDeep } from "@/utils";
type Props = {
data: IDataObject[]
Expand All @@ -32,6 +32,10 @@ const schema = computed<Schema>(() => {
return getSchema(mergeDeep([head, ...tail, head]));
});
const isDataEmpty = computed(() => {
return isEmpty(props.data);
});
const onDragStart = (el: HTMLElement) => {
if (el && el.dataset?.path) {
draggingPath.value = el.dataset.path;
Expand Down Expand Up @@ -67,7 +71,9 @@ const onDragEnd = (el: HTMLElement) => {

<template>
<div :class="$style.schemaWrapper">
<div v-if="isDataEmpty" />
<draggable
v-else
type="mapping"
targetDataKey="mappable"
:disabled="!mappingEnabled"
Expand Down
Loading

0 comments on commit a29dd6e

Please sign in to comment.