fix(editor): Rename the Only you label to Available to you in the credential sharing modal (no-changelog) (#40411)

This commit is contained in:
MHMD 2026-10-07 12:39:05 +00:00 committed by GitHub
parent eaf8c25d1c
commit eac5d1d7b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 107 additions and 17 deletions

View File

@ -1199,8 +1199,10 @@
"credentialEdit.credentialSharing.unshare.confirm.cancelButtonText": "Cancel",
"credentialEdit.credentialSharing.usedIn": "Used in \"{workflowName}\"",
"credentialEdit.credentialSharing.usedIn.count": "Used in {count} workflows",
"credentialEdit.credentialSharing.onlyYou": "Only you",
"credentialEdit.credentialSharing.onlyOwner": "Only {name}",
"credentialEdit.credentialSharing.availableToYou": "Available to you",
"credentialEdit.credentialSharing.availableToYou.tooltip": "Only you can use this credential in {project}. Share it to let others in that project use it.",
"credentialEdit.credentialSharing.availableToOwner": "Available to {name}",
"credentialEdit.credentialSharing.availableToOwner.tooltip": "Only {name} can use this credential in {project}.",
"credentialEdit.credentialSharing.share": "Share",
"credentialEdit.credentialSharing.shareWith": "Share with {project}",
"credentialSelectModal.addNewCredential": "Add new credential",

View File

@ -1,5 +1,6 @@
import { createComponentRenderer } from '@/__tests__/render';
import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/vue';
import { createTestingPinia } from '@pinia/testing';
import { setActivePinia } from 'pinia';
import CredentialSharing from './CredentialSharing.ee.vue';
@ -9,7 +10,7 @@ import { useSettingsStore } from '@n8n/stores/settings.store';
import { useRolesStore } from '@n8n/stores/roles.store';
import type { ICredentialsResponse } from '../../credentials.types';
import { createEventBus } from '@n8n/utils/event-bus';
import { getDropdownItems } from '@/__tests__/utils';
import { getDropdownItems, getTooltip, hoverTooltipTrigger } from '@/__tests__/utils';
import { useI18n } from '@n8n/i18n';
import type * as I18nModule from '@n8n/i18n';
import { ProjectTypes } from '@/features/collaboration/projects/projects.types';
@ -53,8 +54,12 @@ const mockBaseText = vi.fn((key: string, options?: { interpolate?: Record<string
"Can use this credential. Can't edit it.",
'credentialEdit.credentialSharing.usedIn': 'Used in "{workflowName}"',
'credentialEdit.credentialSharing.usedIn.count': 'Used in {count} workflows',
'credentialEdit.credentialSharing.onlyYou': 'Only you',
'credentialEdit.credentialSharing.onlyOwner': 'Only {name}',
'credentialEdit.credentialSharing.availableToYou': 'Available to you',
'credentialEdit.credentialSharing.availableToYou.tooltip':
'Only you can use this credential in {project}.',
'credentialEdit.credentialSharing.availableToOwner': 'Available to {name}',
'credentialEdit.credentialSharing.availableToOwner.tooltip':
'Only {name} can use this credential in {project}.',
'credentialEdit.credentialSharing.share': 'Share',
'credentialEdit.credentialSharing.shareWith': 'Share with {project}',
'auth.roles.owner': 'Owner',
@ -664,7 +669,7 @@ describe('CredentialSharing.ee', () => {
projectsStore.personalProject = ownerPersonalProject;
});
it('shows a project the credential is used in but not shared with, with "Only you" and a Share action', () => {
it('shows a project the credential is used in but not shared with, with "Available to you" and a Share action', () => {
getDependenciesMock.mockReturnValue({
dependencies: [
{
@ -696,7 +701,7 @@ describe('CredentialSharing.ee', () => {
const row = getByTestId('project-sharing-unshared-item');
expect(row).toHaveTextContent('Marketing');
expect(row).toHaveTextContent('Used in "Email summary"');
expect(row).toHaveTextContent('Only you');
expect(row).toHaveTextContent('Available to you');
expect(getByTestId('credential-used-in-project-share')).toHaveTextContent(
'Share with Marketing',
);
@ -774,6 +779,75 @@ describe('CredentialSharing.ee', () => {
expect(getByTestId('credential-used-in-project-share')).toHaveTextContent(/^\s*Share\s*$/);
});
it('explains in a tooltip that only the owner can use the credential in the project', async () => {
getDependenciesMock.mockReturnValue({
dependencies: [
{
id: 'wf-1',
name: 'Email summary',
type: 'workflowParent',
projectId: 'marketing-project',
},
],
inaccessibleCount: 0,
});
const credential = createCredential({
homeProject: ownerPersonalProject,
sharedWithProjects: [],
});
const { getByText } = renderComponent({
props: {
credentialId: credential.id,
credentialData: {},
credentialPermissions: { share: true },
credential,
modalBus: createEventBus(),
},
});
await hoverTooltipTrigger(getByText('Available to you'));
await waitFor(() =>
expect(getTooltip()).toHaveTextContent('Only you can use this credential in Marketing.'),
);
});
it('still explains the access in a tooltip when the project has no name', async () => {
projectsStore.myProjects = [{ ...marketingProject, name: null }];
getDependenciesMock.mockReturnValue({
dependencies: [
{
id: 'wf-1',
name: 'Email summary',
type: 'workflowParent',
projectId: 'marketing-project',
},
],
inaccessibleCount: 0,
});
const credential = createCredential({
homeProject: ownerPersonalProject,
sharedWithProjects: [],
});
const { getByText } = renderComponent({
props: {
credentialId: credential.id,
credentialData: {},
credentialPermissions: { share: true },
credential,
modalBus: createEventBus(),
},
});
await hoverTooltipTrigger(getByText('Available to you'));
await waitFor(() =>
expect(getTooltip()).toHaveTextContent('Only you can use this credential in'),
);
});
it('summarizes as "Used in N workflows" when the credential is used in more than one workflow in the same project', () => {
getDependenciesMock.mockReturnValue({
dependencies: [
@ -943,7 +1017,7 @@ describe('CredentialSharing.ee', () => {
expect(queryByTestId('project-sharing-unshared-item')).not.toBeInTheDocument();
});
it('shows "Only {owner}" instead of "Only you" when the viewer has share permission but is not the credential\'s owner (e.g. an instance admin)', () => {
it('shows "Available to {owner}" instead of "Available to you" when the viewer has share permission but is not the credential\'s owner (e.g. an instance admin)', () => {
// Viewer's own personal project differs from the credential's home
// project, even though they can still share it (broad admin scope).
projectsStore.personalProject = {
@ -984,9 +1058,9 @@ describe('CredentialSharing.ee', () => {
});
const row = getByTestId('project-sharing-unshared-item');
expect(row).toHaveTextContent('Only Mona');
expect(row).not.toHaveTextContent('Only Mona Pfeffer');
expect(queryByText('Only you')).not.toBeInTheDocument();
expect(row).toHaveTextContent('Available to Mona');
expect(row).not.toHaveTextContent('Available to Mona Pfeffer');
expect(queryByText('Available to you')).not.toBeInTheDocument();
expect(getByTestId('credential-used-in-project-share')).toBeInTheDocument();
});
});

View File

@ -26,7 +26,7 @@ import { getResourcePermissions } from '@n8n/permissions';
import { useEnvFeatureFlag } from '@/features/shared/envFeatureFlag/useEnvFeatureFlag';
import { useDependencies } from '@/app/composables/useDependencies';
import { N8nButton, N8nEmptyState, N8nInfoTip, N8nText } from '@n8n/design-system';
import { N8nButton, N8nEmptyState, N8nInfoTip, N8nText, N8nTooltip } from '@n8n/design-system';
type Props = {
credentialId: string;
credentialData: ICredentialDataDecryptedObject;
@ -203,8 +203,8 @@ const usedInProjects = computed(() => {
const usedInAccessText = computed(() =>
isOwnedByViewer.value
? i18n.baseText('credentialEdit.credentialSharing.onlyYou')
: i18n.baseText('credentialEdit.credentialSharing.onlyOwner', {
? i18n.baseText('credentialEdit.credentialSharing.availableToYou')
: i18n.baseText('credentialEdit.credentialSharing.availableToOwner', {
interpolate: { name: credentialOwnerFirstName.value },
}),
);
@ -220,6 +220,18 @@ function usedInShareLabel(project: Pick<ProjectSharingData, 'name'>) {
: i18n.baseText('credentialEdit.credentialSharing.share');
}
function usedInAccessTooltip(project: Pick<ProjectSharingData, 'name'>) {
const projectName = project.name ?? '';
return isOwnedByViewer.value
? i18n.baseText('credentialEdit.credentialSharing.availableToYou.tooltip', {
interpolate: { project: projectName },
})
: i18n.baseText('credentialEdit.credentialSharing.availableToOwner.tooltip', {
interpolate: { name: credentialOwnerFirstName.value, project: projectName },
});
}
function shareUsedInProject(projectId: string) {
const project = projectsStore.myProjects.find((p) => p.id === projectId);
if (!project) return;
@ -314,9 +326,11 @@ function goToUpgrade() {
>
<template #unshared-actions="{ project }">
<div :class="$style.onlyYou">
<N8nText :class="$style.accessText" color="text-light" :title="usedInAccessText">
{{ usedInAccessText }}
</N8nText>
<N8nTooltip :content="usedInAccessTooltip(project)" placement="top">
<N8nText :class="$style.accessText" color="text-light">
{{ usedInAccessText }}
</N8nText>
</N8nTooltip>
<N8nButton
variant="outline"
data-test-id="credential-used-in-project-share"