Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editor): Update ownership pills #11155

Merged
2 changes: 1 addition & 1 deletion cypress/e2e/17-sharing.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
credentialsPage.getters
.credentialCards()
.should('have.length', 2)
.filter(':contains("Owned by me")')
.filter(':contains("Personal")')
.should('have.length', 1);
});
});
Expand Down
49 changes: 24 additions & 25 deletions cypress/e2e/39-projects.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@ import {
NDV,
MainSidebar,
} from '../pages';
import {
getVisibleDropdown,
getVisibleModalOverlay,
getVisibleSelect,
getVisiblePopper,
} from '../utils';
import { clearNotifications } from '../pages/notifications';
import { getVisibleDropdown, getVisibleModalOverlay, getVisibleSelect } from '../utils';

const workflowsPage = new WorkflowsPage();
const workflowPage = new WorkflowPage();
Expand Down Expand Up @@ -453,38 +449,48 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters.workflowCards().should('not.have.length');
workflowsPage.getters.newWorkflowButtonCard().click();
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Home project');
clearNotifications();

projects.getHomeButton().click();
projects.getProjectTabCredentials().should('be.visible').click();
credentialsPage.getters.emptyListCreateCredentialButton().click();
projects.createCredential('Credential in Home project');

clearNotifications();

// Create a project and add a credential and a workflow to it
projects.createProject('Project 1');
clearNotifications();
projects.getProjectTabCredentials().click();
credentialsPage.getters.emptyListCreateCredentialButton().click();
projects.createCredential('Credential in Project 1');
clearNotifications();

projects.getProjectTabWorkflows().click();
workflowsPage.getters.newWorkflowButtonCard().click();
projects.createWorkflow('Test_workflow_1.json', 'Workflow in Project 1');

clearNotifications();

// Create another project and add a credential and a workflow to it
projects.createProject('Project 2');
clearNotifications();
projects.getProjectTabCredentials().click();
credentialsPage.getters.emptyListCreateCredentialButton().click();
projects.createCredential('Credential in Project 2');
clearNotifications();

projects.getProjectTabWorkflows().click();
workflowsPage.getters.newWorkflowButtonCard().click();
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();
workflowsPage.getters
.workflowCards()
.should('have.length', 3)
.filter(':contains("Owned by me")')
.filter(':contains("Personal")')
.should('exist');
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
workflowsPage.getters.workflowMoveButton().click();
Expand All @@ -501,11 +507,12 @@ describe('Projects', { disableAutoLogin: true }, () => {
.filter(':contains("Project 1")')
.click();
projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
clearNotifications();

workflowsPage.getters
.workflowCards()
.should('have.length', 3)
.filter(':contains("Owned by me")')
.filter(':contains("Personal")')
.should('not.exist');

// Move the workflow from Project 1 to Project 2
Expand All @@ -532,6 +539,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters.workflowCards().should('have.length', 2);
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
workflowsPage.getters.workflowMoveButton().click();
clearNotifications();

projects
.getResourceMoveModal()
Expand Down Expand Up @@ -571,10 +579,11 @@ describe('Projects', { disableAutoLogin: true }, () => {
.click();

projects.getResourceMoveModal().find('button:contains("Move workflow")').click();
clearNotifications();
workflowsPage.getters
.workflowCards()
.should('have.length', 3)
.filter(':contains("Owned by me")')
.filter(':contains("Personal")')
.should('have.length', 1);

// Move the credential from Project 1 to Project 2
Expand All @@ -584,9 +593,6 @@ describe('Projects', { disableAutoLogin: true }, () => {
credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
credentialsPage.getters.credentialMoveButton().click();

// wait for all poppers to be gone
getVisiblePopper().should('have.length', 0);

projects
.getResourceMoveModal()
.should('be.visible')
Expand All @@ -599,7 +605,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
.filter(':contains("Project 2")')
.click();
projects.getResourceMoveModal().find('button:contains("Move credential")').click();

clearNotifications();
credentialsPage.getters.credentialCards().should('not.have.length');

// Move the credential from Project 2 to admin user
Expand All @@ -610,9 +616,6 @@ describe('Projects', { disableAutoLogin: true }, () => {
credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
credentialsPage.getters.credentialMoveButton().click();

// wait for all poppers to be gone
getVisiblePopper().should('have.length', 0);

projects
.getResourceMoveModal()
.should('be.visible')
Expand All @@ -635,9 +638,6 @@ describe('Projects', { disableAutoLogin: true }, () => {
credentialsPage.getters.credentialCardActions('Credential in Project 1').click();
credentialsPage.getters.credentialMoveButton().click();

// wait for all poppers to be gone
getVisiblePopper().should('have.length', 0);

projects
.getResourceMoveModal()
.should('be.visible')
Expand All @@ -651,13 +651,12 @@ describe('Projects', { disableAutoLogin: true }, () => {
.click();
projects.getResourceMoveModal().find('button:contains("Move credential")').click();

// wait for all poppers to be gone
getVisiblePopper().should('have.length', 0);
clearNotifications();

credentialsPage.getters
.credentialCards()
.should('have.length', 3)
.filter(':contains("Owned by me")')
.filter(':contains("Personal")')
.should('have.length', 2);

// Move the credential from admin user back to its original project (Project 1)
Expand Down Expand Up @@ -716,7 +715,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters
.workflowCards()
.should('have.length', 1)
.filter(':contains("Owned by me")')
.filter(':contains("Personal")')
.should('exist');
workflowsPage.getters.workflowCardActions('My workflow').click();
workflowsPage.getters.workflowMoveButton().click();
Expand All @@ -737,7 +736,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters
.workflowCards()
.should('have.length', 1)
.filter(':contains("Owned by me")')
.filter(':contains("Personal")')
.should('not.exist');

//Log out with instance owner and log in with the member user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@ import { truncate } from 'n8n-design-system';
const renderComponent = createComponentRenderer(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({
props: {
resource: {},
personalProject: {},
},
});

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({
props: {
resource: {
homeProject: {
id: '1',
name: 'John',
},
},
resourceType: 'workflow',
Expand All @@ -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([
Expand Down
65 changes: 56 additions & 9 deletions packages/editor-ui/src/components/Projects/ProjectCardBadge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,25 +52,30 @@ const projectState = computed(() => {
}
return ProjectState.Unknown;
});

const numberOfMembersInHomeTeamProject = computed(
() => props.resource.sharedWithProjects?.length ?? 0,
);
Comment on lines +56 to +58
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cstuncsik please double check this is the correct source of truth

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the intention is here (maybe the variable name is confusing)

If we want to show how many users the resource is shared with, then it's correct.
If we want to show how many users are there in the home project of the resource, then it's not
If the latter you need to get the home project of the resource (by the homeProject.id) and check the length of its relations

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to the extend of my knowledge.. we want to share how many users the resource is shared with, i'll ask in the ticket to double check

Copy link
Contributor

@cstuncsik cstuncsik Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the easier, the other one could only be problematic in the Home project because the resources are listed from all available projects and you would need to get all the relations for all the projects

In a team project you just get it from the store (projectsStore.currentProject.relations)


const badgeText = computed(() => {
if (
projectState.value === ProjectState.Owned ||
projectState.value === ProjectState.SharedOwned
) {
return i18n.baseText('generic.ownedByMe');
return i18n.baseText('projects.menu.personal');
} else {
const { name, email } = splitName(props.resource.homeProject?.name ?? '');
return name ?? email ?? '';
}
});
const badgeIcon = computed(() => {
switch (projectState.value) {
case ProjectState.SharedPersonal:
case ProjectState.Owned:
case ProjectState.SharedOwned:
return 'user-friends';
return 'user';
case ProjectState.Team:
case ProjectState.SharedTeam:
return 'archive';
return 'layer-group';
default:
return '';
}
Expand All @@ -81,13 +86,15 @@ const badgeTooltip = computed(() => {
return i18n.baseText('projects.badge.tooltip.sharedOwned', {
interpolate: {
resourceTypeLabel: props.resourceTypeLabel,
count: numberOfMembersInHomeTeamProject.value,
},
});
case ProjectState.SharedPersonal:
return i18n.baseText('projects.badge.tooltip.sharedPersonal', {
interpolate: {
resourceTypeLabel: props.resourceTypeLabel,
name: badgeText.value,
count: numberOfMembersInHomeTeamProject.value,
},
});
case ProjectState.Personal:
Expand All @@ -109,6 +116,7 @@ const badgeTooltip = computed(() => {
interpolate: {
resourceTypeLabel: props.resourceTypeLabel,
name: badgeText.value,
count: numberOfMembersInHomeTeamProject.value,
},
});
default:
Expand All @@ -118,14 +126,53 @@ const badgeTooltip = computed(() => {
</script>
<template>
<N8nTooltip :disabled="!badgeTooltip" placement="top">
<N8nBadge v-if="badgeText" class="mr-xs" theme="tertiary" bold data-test-id="card-badge">
<span v-n8n-truncate:20>{{ badgeText }}</span>
<N8nIcon v-if="badgeIcon" :icon="badgeIcon" size="small" class="ml-5xs" />
</N8nBadge>
<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>
</N8nBadge>
<N8nBadge
v-if="numberOfMembersInHomeTeamProject"
:class="[$style.badge, $style.countBadge]"
theme="tertiary"
bold
>
+ {{ numberOfMembersInHomeTeamProject }}
</N8nBadge>
</div>
<template #content>
{{ badgeTooltip }}
</template>
</N8nTooltip>
</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>
8 changes: 4 additions & 4 deletions packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"generic.seePlans": "See plans",
"generic.loading": "Loading",
"generic.and": "and",
"generic.ownedByMe": "Owned by me",
"generic.ownedByMe": "(You)",
"generic.moreInfo": "More info",
"generic.next": "Next",
"about.aboutN8n": "About n8n",
Expand Down Expand Up @@ -2543,11 +2543,11 @@
"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.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.sharedPersonal": "This {resourceTypeLabel} is owned by {name} 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 {count} users",
"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.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.invalidCode": "Two-factor code failed. Please try again.",
"mfa.code.modal.title": "Two-factor authentication",
Expand Down
Loading