feat(editor): Update ownership pills (#11155)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions

This commit is contained in:
Raúl Gómez Morales 2024-10-28 08:48:51 +01:00 committed by GitHub
parent 351134f786
commit 8147038cf8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 110 additions and 43 deletions

View file

@ -259,7 +259,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
credentialsPage.getters credentialsPage.getters
.credentialCards() .credentialCards()
.should('have.length', 2) .should('have.length', 2)
.filter(':contains("Owned by me")') .filter(':contains("Personal")')
.should('have.length', 1); .should('have.length', 1);
}); });
}); });

View file

@ -15,12 +15,8 @@ import {
NDV, NDV,
MainSidebar, MainSidebar,
} from '../pages'; } from '../pages';
import { import { clearNotifications } from '../pages/notifications';
getVisibleDropdown, import { getVisibleDropdown, getVisibleModalOverlay, getVisibleSelect } from '../utils';
getVisibleModalOverlay,
getVisibleSelect,
getVisiblePopper,
} from '../utils';
const workflowsPage = new WorkflowsPage(); const workflowsPage = new WorkflowsPage();
const workflowPage = new WorkflowPage(); const workflowPage = new WorkflowPage();
@ -453,38 +449,48 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters.workflowCards().should('not.have.length'); workflowsPage.getters.workflowCards().should('not.have.length');
workflowsPage.getters.newWorkflowButtonCard().click(); workflowsPage.getters.newWorkflowButtonCard().click();
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Home project'); projects.createWorkflow('Test_workflow_1.json', 'Workflow in Home project');
clearNotifications();
projects.getHomeButton().click(); projects.getHomeButton().click();
projects.getProjectTabCredentials().should('be.visible').click(); projects.getProjectTabCredentials().should('be.visible').click();
credentialsPage.getters.emptyListCreateCredentialButton().click(); credentialsPage.getters.emptyListCreateCredentialButton().click();
projects.createCredential('Credential in Home project'); projects.createCredential('Credential in Home project');
clearNotifications();
// Create a project and add a credential and a workflow to it // Create a project and add a credential and a workflow to it
projects.createProject('Project 1'); projects.createProject('Project 1');
clearNotifications();
projects.getProjectTabCredentials().click(); projects.getProjectTabCredentials().click();
credentialsPage.getters.emptyListCreateCredentialButton().click(); credentialsPage.getters.emptyListCreateCredentialButton().click();
projects.createCredential('Credential in Project 1'); projects.createCredential('Credential in Project 1');
clearNotifications();
projects.getProjectTabWorkflows().click(); projects.getProjectTabWorkflows().click();
workflowsPage.getters.newWorkflowButtonCard().click(); workflowsPage.getters.newWorkflowButtonCard().click();
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Project 1'); projects.createWorkflow('Test_workflow_1.json', 'Workflow in Project 1');
clearNotifications();
// Create another project and add a credential and a workflow to it // Create another project and add a credential and a workflow to it
projects.createProject('Project 2'); projects.createProject('Project 2');
clearNotifications();
projects.getProjectTabCredentials().click(); projects.getProjectTabCredentials().click();
credentialsPage.getters.emptyListCreateCredentialButton().click(); credentialsPage.getters.emptyListCreateCredentialButton().click();
projects.createCredential('Credential in Project 2'); projects.createCredential('Credential in Project 2');
clearNotifications();
projects.getProjectTabWorkflows().click(); projects.getProjectTabWorkflows().click();
workflowsPage.getters.newWorkflowButtonCard().click(); workflowsPage.getters.newWorkflowButtonCard().click();
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Project 2'); projects.createWorkflow('Test_workflow_1.json', 'Workflow in Project 2');
clearNotifications();
// Move the workflow owned by me from Home to Project 1 // Move the workflow Personal from Home to Project 1
projects.getHomeButton().click(); projects.getHomeButton().click();
workflowsPage.getters workflowsPage.getters
.workflowCards() .workflowCards()
.should('have.length', 3) .should('have.length', 3)
.filter(':contains("Owned by me")') .filter(':contains("Personal")')
.should('exist'); .should('exist');
workflowsPage.getters.workflowCardActions('Workflow in Home project').click(); workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
workflowsPage.getters.workflowMoveButton().click(); workflowsPage.getters.workflowMoveButton().click();
@ -501,11 +507,12 @@ describe('Projects', { disableAutoLogin: true }, () => {
.filter(':contains("Project 1")') .filter(':contains("Project 1")')
.click(); .click();
projects.getResourceMoveModal().find('button:contains("Move workflow")').click(); projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
clearNotifications();
workflowsPage.getters workflowsPage.getters
.workflowCards() .workflowCards()
.should('have.length', 3) .should('have.length', 3)
.filter(':contains("Owned by me")') .filter(':contains("Personal")')
.should('not.exist'); .should('not.exist');
// Move the workflow from Project 1 to Project 2 // Move the workflow from Project 1 to Project 2
@ -532,6 +539,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters.workflowCards().should('have.length', 2); workflowsPage.getters.workflowCards().should('have.length', 2);
workflowsPage.getters.workflowCardActions('Workflow in Home project').click(); workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
workflowsPage.getters.workflowMoveButton().click(); workflowsPage.getters.workflowMoveButton().click();
clearNotifications();
projects projects
.getResourceMoveModal() .getResourceMoveModal()
@ -571,10 +579,11 @@ describe('Projects', { disableAutoLogin: true }, () => {
.click(); .click();
projects.getResourceMoveModal().find('button:contains("Move workflow")').click(); projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
clearNotifications();
workflowsPage.getters workflowsPage.getters
.workflowCards() .workflowCards()
.should('have.length', 3) .should('have.length', 3)
.filter(':contains("Owned by me")') .filter(':contains("Personal")')
.should('have.length', 1); .should('have.length', 1);
// Move the credential from Project 1 to Project 2 // Move the credential from Project 1 to Project 2
@ -584,9 +593,6 @@ describe('Projects', { disableAutoLogin: true }, () => {
credentialsPage.getters.credentialCardActions('Credential in Project 1').click(); credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
credentialsPage.getters.credentialMoveButton().click(); credentialsPage.getters.credentialMoveButton().click();
// wait for all poppers to be gone
getVisiblePopper().should('have.length', 0);
projects projects
.getResourceMoveModal() .getResourceMoveModal()
.should('be.visible') .should('be.visible')
@ -599,7 +605,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
.filter(':contains("Project 2")') .filter(':contains("Project 2")')
.click(); .click();
projects.getResourceMoveModal().find('button:contains("Move credential")').click(); projects.getResourceMoveModal().find('button:contains("Move credential")').click();
clearNotifications();
credentialsPage.getters.credentialCards().should('not.have.length'); credentialsPage.getters.credentialCards().should('not.have.length');
// Move the credential from Project 2 to admin user // Move the credential from Project 2 to admin user
@ -610,9 +616,6 @@ describe('Projects', { disableAutoLogin: true }, () => {
credentialsPage.getters.credentialCardActions('Credential in Project 1').click(); credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
credentialsPage.getters.credentialMoveButton().click(); credentialsPage.getters.credentialMoveButton().click();
// wait for all poppers to be gone
getVisiblePopper().should('have.length', 0);
projects projects
.getResourceMoveModal() .getResourceMoveModal()
.should('be.visible') .should('be.visible')
@ -635,9 +638,6 @@ describe('Projects', { disableAutoLogin: true }, () => {
credentialsPage.getters.credentialCardActions('Credential in Project 1').click(); credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
credentialsPage.getters.credentialMoveButton().click(); credentialsPage.getters.credentialMoveButton().click();
// wait for all poppers to be gone
getVisiblePopper().should('have.length', 0);
projects projects
.getResourceMoveModal() .getResourceMoveModal()
.should('be.visible') .should('be.visible')
@ -651,13 +651,12 @@ describe('Projects', { disableAutoLogin: true }, () => {
.click(); .click();
projects.getResourceMoveModal().find('button:contains("Move credential")').click(); projects.getResourceMoveModal().find('button:contains("Move credential")').click();
// wait for all poppers to be gone clearNotifications();
getVisiblePopper().should('have.length', 0);
credentialsPage.getters credentialsPage.getters
.credentialCards() .credentialCards()
.should('have.length', 3) .should('have.length', 3)
.filter(':contains("Owned by me")') .filter(':contains("Personal")')
.should('have.length', 2); .should('have.length', 2);
// Move the credential from admin user back to its original project (Project 1) // Move the credential from admin user back to its original project (Project 1)
@ -716,7 +715,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters workflowsPage.getters
.workflowCards() .workflowCards()
.should('have.length', 1) .should('have.length', 1)
.filter(':contains("Owned by me")') .filter(':contains("Personal")')
.should('exist'); .should('exist');
workflowsPage.getters.workflowCardActions('My workflow').click(); workflowsPage.getters.workflowCardActions('My workflow').click();
workflowsPage.getters.workflowMoveButton().click(); workflowsPage.getters.workflowMoveButton().click();
@ -737,7 +736,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters workflowsPage.getters
.workflowCards() .workflowCards()
.should('have.length', 1) .should('have.length', 1)
.filter(':contains("Owned by me")') .filter(':contains("Personal")')
.should('not.exist'); .should('not.exist');
//Log out with instance owner and log in with the member user //Log out with instance owner and log in with the member user

View file

@ -5,7 +5,7 @@ import { truncate } from 'n8n-design-system';
const renderComponent = createComponentRenderer(ProjectCardBadge); const renderComponent = createComponentRenderer(ProjectCardBadge);
describe('ProjectCardBadge', () => { describe('ProjectCardBadge', () => {
it('should show "Owned by me" badge if there is no homeProject', () => { it('should show "Personal" badge if there is no homeProject', () => {
const { getByText } = renderComponent({ const { getByText } = renderComponent({
props: { props: {
resource: {}, resource: {},
@ -13,15 +13,16 @@ describe('ProjectCardBadge', () => {
}, },
}); });
expect(getByText('Owned by me')).toBeVisible(); expect(getByText('Personal')).toBeVisible();
}); });
it('should show "Owned by me" badge if homeProject ID equals personalProject ID', () => { it('should show "Personal" badge if homeProject ID equals personalProject ID', () => {
const { getByText } = renderComponent({ const { getByText } = renderComponent({
props: { props: {
resource: { resource: {
homeProject: { homeProject: {
id: '1', id: '1',
name: 'John',
}, },
}, },
resourceType: 'workflow', resourceType: 'workflow',
@ -31,7 +32,27 @@ describe('ProjectCardBadge', () => {
}, },
}); });
expect(getByText('Owned by me')).toBeVisible(); expect(getByText('Personal')).toBeVisible();
});
it('should show shared with count', () => {
const { getByText } = renderComponent({
props: {
resource: {
sharedWithProjects: [{}, {}, {}],
homeProject: {
id: '1',
name: 'John',
},
},
resourceType: 'workflow',
personalProject: {
id: '1',
},
},
});
expect(getByText('+ 3')).toBeVisible();
}); });
test.each([ test.each([

View file

@ -52,12 +52,17 @@ const projectState = computed(() => {
} }
return ProjectState.Unknown; return ProjectState.Unknown;
}); });
const numberOfMembersInHomeTeamProject = computed(
() => props.resource.sharedWithProjects?.length ?? 0,
);
const badgeText = computed(() => { const badgeText = computed(() => {
if ( if (
projectState.value === ProjectState.Owned || projectState.value === ProjectState.Owned ||
projectState.value === ProjectState.SharedOwned projectState.value === ProjectState.SharedOwned
) { ) {
return i18n.baseText('generic.ownedByMe'); return i18n.baseText('projects.menu.personal');
} else { } else {
const { name, email } = splitName(props.resource.homeProject?.name ?? ''); const { name, email } = splitName(props.resource.homeProject?.name ?? '');
return name ?? email ?? ''; return name ?? email ?? '';
@ -65,12 +70,12 @@ const badgeText = computed(() => {
}); });
const badgeIcon = computed(() => { const badgeIcon = computed(() => {
switch (projectState.value) { switch (projectState.value) {
case ProjectState.SharedPersonal: case ProjectState.Owned:
case ProjectState.SharedOwned: case ProjectState.SharedOwned:
return 'user-friends'; return 'user';
case ProjectState.Team: case ProjectState.Team:
case ProjectState.SharedTeam: case ProjectState.SharedTeam:
return 'archive'; return 'layer-group';
default: default:
return ''; return '';
} }
@ -81,6 +86,7 @@ const badgeTooltip = computed(() => {
return i18n.baseText('projects.badge.tooltip.sharedOwned', { return i18n.baseText('projects.badge.tooltip.sharedOwned', {
interpolate: { interpolate: {
resourceTypeLabel: props.resourceTypeLabel, resourceTypeLabel: props.resourceTypeLabel,
count: numberOfMembersInHomeTeamProject.value,
}, },
}); });
case ProjectState.SharedPersonal: case ProjectState.SharedPersonal:
@ -88,6 +94,7 @@ const badgeTooltip = computed(() => {
interpolate: { interpolate: {
resourceTypeLabel: props.resourceTypeLabel, resourceTypeLabel: props.resourceTypeLabel,
name: badgeText.value, name: badgeText.value,
count: numberOfMembersInHomeTeamProject.value,
}, },
}); });
case ProjectState.Personal: case ProjectState.Personal:
@ -109,6 +116,7 @@ const badgeTooltip = computed(() => {
interpolate: { interpolate: {
resourceTypeLabel: props.resourceTypeLabel, resourceTypeLabel: props.resourceTypeLabel,
name: badgeText.value, name: badgeText.value,
count: numberOfMembersInHomeTeamProject.value,
}, },
}); });
default: default:
@ -118,14 +126,53 @@ const badgeTooltip = computed(() => {
</script> </script>
<template> <template>
<N8nTooltip :disabled="!badgeTooltip" placement="top"> <N8nTooltip :disabled="!badgeTooltip" placement="top">
<N8nBadge v-if="badgeText" class="mr-xs" theme="tertiary" bold data-test-id="card-badge"> <div class="mr-xs">
<N8nBadge
v-if="badgeText"
:class="$style.badge"
theme="tertiary"
bold
data-test-id="card-badge"
>
<N8nIcon v-if="badgeIcon" :icon="badgeIcon" size="small" class="mr-3xs" />
<span v-n8n-truncate:20>{{ badgeText }}</span> <span v-n8n-truncate:20>{{ badgeText }}</span>
<N8nIcon v-if="badgeIcon" :icon="badgeIcon" size="small" class="ml-5xs" />
</N8nBadge> </N8nBadge>
<N8nBadge
v-if="numberOfMembersInHomeTeamProject"
:class="[$style.badge, $style.countBadge]"
theme="tertiary"
bold
>
+ {{ numberOfMembersInHomeTeamProject }}
</N8nBadge>
</div>
<template #content> <template #content>
{{ badgeTooltip }} {{ badgeTooltip }}
</template> </template>
</N8nTooltip> </N8nTooltip>
</template> </template>
<style lang="scss" module></style> <style lang="scss" module>
.badge {
padding: var(--spacing-4xs) var(--spacing-2xs);
background-color: var(--color-background-xlight);
border-color: var(--color-foreground-base);
z-index: 1;
position: relative;
height: 23px;
:global(.n8n-text) {
color: var(--color-text-base);
}
}
.countBadge {
margin-left: -5px;
z-index: 0;
position: relative;
height: 23px;
:global(.n8n-text) {
color: var(--color-text-light);
}
}
</style>

View file

@ -71,7 +71,7 @@
"generic.seePlans": "See plans", "generic.seePlans": "See plans",
"generic.loading": "Loading", "generic.loading": "Loading",
"generic.and": "and", "generic.and": "and",
"generic.ownedByMe": "Owned by me", "generic.ownedByMe": "(You)",
"generic.moreInfo": "More info", "generic.moreInfo": "More info",
"generic.next": "Next", "generic.next": "Next",
"about.aboutN8n": "About n8n", "about.aboutN8n": "About n8n",
@ -2543,11 +2543,11 @@
"projects.move.resource.success.message": "{resourceName} {resourceTypeLabel} was moved to {targetProjectName}. {workflow} {link}", "projects.move.resource.success.message": "{resourceName} {resourceTypeLabel} was moved to {targetProjectName}. {workflow} {link}",
"projects.move.resource.success.message.workflow": "Please double check any credentials this workflow is using are also shared with {targetProjectName}.", "projects.move.resource.success.message.workflow": "Please double check any credentials this workflow is using are also shared with {targetProjectName}.",
"projects.move.resource.success.link": "View {targetProjectName}", "projects.move.resource.success.link": "View {targetProjectName}",
"projects.badge.tooltip.sharedOwned": "This {resourceTypeLabel} is owned by you and shared with one or more projects or users", "projects.badge.tooltip.sharedOwned": "This {resourceTypeLabel} is owned by you and shared with {count} users",
"projects.badge.tooltip.sharedPersonal": "This {resourceTypeLabel} is owned by {name} and shared with one or more projects or users", "projects.badge.tooltip.sharedPersonal": "This {resourceTypeLabel} is owned by {name} and shared with {count} users",
"projects.badge.tooltip.personal": "This {resourceTypeLabel} is owned by {name}", "projects.badge.tooltip.personal": "This {resourceTypeLabel} is owned by {name}",
"projects.badge.tooltip.team": "This {resourceTypeLabel} is owned and accessible by the {name} project.", "projects.badge.tooltip.team": "This {resourceTypeLabel} is owned and accessible by the {name} project.",
"projects.badge.tooltip.sharedTeam": "This {resourceTypeLabel} is owned and accessible by the {name} project and shared with one or more projects or users", "projects.badge.tooltip.sharedTeam": "This {resourceTypeLabel} is owned by the {name} project and accessible by {count} users",
"mfa.setup.invalidAuthenticatorCode": "{code} is not a valid number", "mfa.setup.invalidAuthenticatorCode": "{code} is not a valid number",
"mfa.setup.invalidCode": "Two-factor code failed. Please try again.", "mfa.setup.invalidCode": "Two-factor code failed. Please try again.",
"mfa.code.modal.title": "Two-factor authentication", "mfa.code.modal.title": "Two-factor authentication",