fix(editor): Keep the used-in rows on the credential sharing tab when personal-space sharing is disabled (#41207)

This commit is contained in:
MHMD 2026-10-16 10:40:31 +00:00 committed by GitHub
parent a94f5dc8b7
commit ca9661c091
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 102 additions and 25 deletions

View File

@ -1058,8 +1058,8 @@ describe('CredentialSharing.ee', () => {
expect(queryByTestId('project-sharing-unshared-item')).not.toBeInTheDocument();
});
it('does not show used-in projects when the viewer cannot share', () => {
getDependenciesMock.mockReturnValue({
describe('when the viewer cannot share', () => {
const usedInMarketing = {
dependencies: [
{
id: 'wf-1',
@ -1069,24 +1069,86 @@ describe('CredentialSharing.ee', () => {
},
],
inaccessibleCount: 0,
};
it('keeps the used-in row and disables the Share button when sharing is disabled in personal spaces', async () => {
getDependenciesMock.mockReturnValue(usedInMarketing);
const credential = createCredential({
homeProject: ownerPersonalProject,
sharedWithProjects: [],
});
const { getByTestId, findAllByText } = renderComponent({
props: {
credentialId: credential.id,
credentialData: {},
credentialPermissions: { share: false },
credential,
modalBus: createEventBus(),
},
});
expect(fetchDependenciesMock).toHaveBeenCalledWith([credential.id], 'credential');
const row = getByTestId('project-sharing-unshared-item');
expect(row).toHaveTextContent('Marketing');
expect(row).toHaveTextContent('Used in "Email summary"');
const share = getByTestId('credential-used-in-project-share');
expect(share).toBeDisabled();
await userEvent.hover(share.parentElement as HTMLElement);
expect(
await findAllByText("You don't have permission to share personal credentials"),
).not.toHaveLength(0);
});
const credential = createCredential({
homeProject: ownerPersonalProject,
sharedWithProjects: [],
});
const { queryByTestId } = renderComponent({
props: {
credentialId: credential.id,
credentialData: {},
credentialPermissions: { share: false },
credential,
modalBus: createEventBus(),
},
it('does not share the project when the disabled Share button is clicked', async () => {
getDependenciesMock.mockReturnValue(usedInMarketing);
const credential = createCredential({
homeProject: ownerPersonalProject,
sharedWithProjects: [],
});
const { getByTestId, emitted } = renderComponent({
props: {
credentialId: credential.id,
credentialData: {},
credentialPermissions: { share: false },
credential,
modalBus: createEventBus(),
},
});
await userEvent.click(getByTestId('credential-used-in-project-share'));
expect(emitted()['update:modelValue']).toBeUndefined();
expect(getByTestId('project-sharing-unshared-item')).toBeInTheDocument();
});
expect(queryByTestId('project-sharing-unshared-item')).not.toBeInTheDocument();
expect(fetchDependenciesMock).not.toHaveBeenCalled();
it('shows a disabled Share button without the personal-space tooltip when the credential lives in a team project', async () => {
getDependenciesMock.mockReturnValue(usedInMarketing);
const credential = createCredential({ sharedWithProjects: [] });
const { getByTestId, queryByText } = renderComponent({
props: {
credentialId: credential.id,
credentialData: {},
credentialPermissions: { share: false },
credential,
modalBus: createEventBus(),
},
});
expect(getByTestId('project-sharing-unshared-item')).toBeInTheDocument();
const share = getByTestId('credential-used-in-project-share');
expect(share).toBeDisabled();
await userEvent.hover(share.parentElement as HTMLElement);
expect(
queryByText("You don't have permission to share personal credentials"),
).not.toBeInTheDocument();
});
});
it('does not fetch or show used-in projects when the flag is disabled', () => {

View File

@ -156,13 +156,18 @@ const confirmRemoval = computed(() => {
const { fetchDependencies, getDependencies } = useDependencies();
onMounted(() => {
if (isCredSharingEnabled.value && props.credentialPermissions.share) {
if (isCredSharingEnabled.value) {
void fetchDependencies([props.credentialId], 'credential');
}
});
/**
* Projects where the credential is used without a share. The rows stay visible
* when the viewer cannot share: where a credential is used is audit information,
* so the share permission only gates the Share button.
*/
const usedInProjects = computed(() => {
if (!isCredSharingEnabled.value || !props.credentialPermissions.share) return [];
if (!isCredSharingEnabled.value) return [];
const deps = getDependencies(props.credentialId, 'credential');
if (!deps) return [];
@ -215,6 +220,12 @@ function usedInProjectName(project: Pick<ProjectSharingData, 'name'>) {
}
function usedInShareTooltip(project: ProjectSharingData) {
if (!props.credentialPermissions.share) {
return isPersonalSpaceRestricted.value
? i18n.baseText('credentialEdit.credentialSharing.info.personalSpaceRestricted')
: '';
}
const name = usedInProjectName(project);
if (hasInstanceWideUse(project)) {
@ -373,13 +384,17 @@ function goToUpgrade() {
:disabled="!usedInShareTooltip(project)"
placement="top"
>
<N8nButton
variant="outline"
data-test-id="credential-used-in-project-share"
@click="shareUsedInProject(project.id)"
>
{{ i18n.baseText('credentialEdit.credentialSharing.share') }}
</N8nButton>
<!-- A disabled button gets no hover events, so the span carries the tooltip -->
<span>
<N8nButton
variant="outline"
:disabled="!credentialPermissions.share"
data-test-id="credential-used-in-project-share"
@click="shareUsedInProject(project.id)"
>
{{ i18n.baseText('credentialEdit.credentialSharing.share') }}
</N8nButton>
</span>
</N8nTooltip>
</div>
</template>