From 6ed865136e08b5946f9e39df61b5684752fcb7dc Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 22:58:14 -0400 Subject: [PATCH 01/21] feat: ui:disabled allowed in EntityPicker Signed-off-by: Nikunj Hudka --- .../fields/EntityPicker/EntityPicker.test.tsx | 68 +++++++++++++++++++ .../fields/EntityPicker/EntityPicker.tsx | 9 ++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index 3f8b1adc5a..6b5f3ee83f 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -28,6 +28,7 @@ import { EntityPickerProps } from './schema'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import userEvent from '@testing-library/user-event'; const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', @@ -261,6 +262,73 @@ describe('', () => { }); }); + describe('ui:disabled EntityPicker', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + }; + props = { + onChange, + schema, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + it('Prevents user from modifying input when ui:disabled is true', async () => { + props.uiSchema = { 'ui:disabled': true }; + props.formData = 'component:default/myentity'; + + await renderInTestApp( + + + , + ); + + const input = screen.getByRole('textbox'); + + // Expect input to be disabled + expect(input).toBeDisabled(); + expect(input).toHaveValue('component:default/myentity'); + }); + + it('Allows user to edit when ui:disabled is false', async () => { + props.uiSchema = { 'ui:disabled': false }; + props.formData = 'component:default/myentity'; + + await renderInTestApp( + + + , + ); + + const input = screen.getByRole('textbox'); + expect(input).not.toBeDisabled(); + + fireEvent.change(input, { + target: { value: 'component:default/mynewentity' }, + }); + fireEvent.blur(input); + + expect(input).toHaveValue('component:default/mynewentity'); + expect(onChange).toHaveBeenCalledWith('component:default/mynewentity'); + }); + }); + describe('catalogFilter should take precedence over allowedKinds', () => { beforeEach(() => { uiSchema = { diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index e1f34638dc..32cdcd73f4 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -73,6 +73,7 @@ export const EntityPicker = (props: EntityPickerProps) => { const defaultKind = uiSchema['ui:options']?.defaultKind; const defaultNamespace = uiSchema['ui:options']?.defaultNamespace || undefined; + const isDisabled = uiSchema?.['ui:disabled'] ?? false; const catalogApi = useApi(catalogApiRef); const entityPresentationApi = useApi(entityPresentationApiRef); @@ -185,9 +186,10 @@ export const EntityPicker = (props: EntityPickerProps) => { > { FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} variant="outlined" required={required} + disabled={isDisabled} InputProps={params.InputProps} /> )} From 4c6c47b401d5766d77abc5c90652103c5a809219 Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 22:58:54 -0400 Subject: [PATCH 02/21] feat: ui:disabled allowed in EntityTagsPicker Signed-off-by: Nikunj Hudka --- .../components/fields/EntityTagsPicker/EntityTagsPicker.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx index 25b5d926a5..ed09e03c13 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx @@ -45,6 +45,8 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { const kinds = uiSchema['ui:options']?.kinds; const showCounts = uiSchema['ui:options']?.showCounts; const helperText = uiSchema['ui:options']?.helperText; + const isDisabled = uiSchema?.['ui:disabled'] ?? false; + const { t } = useTranslationRef(scaffolderTranslationRef); const { loading, value: existingTags } = useAsync(async () => { @@ -101,6 +103,7 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { freeSolo filterSelectedOptions onChange={setTags} + disabled={isDisabled} value={formData || []} inputValue={inputValue} loading={loading} @@ -113,6 +116,7 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { setInputValue(e.target.value)} error={inputError} helperText={helperText ?? t('fields.entityTagsPicker.description')} From 3bed6958fa3fb46548e69395be9befeea51bc4eb Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:01:19 -0400 Subject: [PATCH 03/21] feat: ui:disabled allowed in MultiEntityPicker Signed-off-by: Nikunj Hudka --- .../MultiEntityPicker.test.tsx | 32 +++++++++++++++++++ .../MultiEntityPicker/MultiEntityPicker.tsx | 8 ++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx index 74b9dadda3..2839ce1731 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx @@ -407,6 +407,38 @@ describe('', () => { }); }); + describe('ui:disabled MultiEntityPicker', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + allowArbitraryValues: true, + }, + 'ui:disabled': true, + }; + props = { + onChange, + schema, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + it('Prevents user from modifying input when ui:disabled is true', async () => { + props.formData = ['component/default:myentity']; + await renderInTestApp( + + + , + ); + + const input = screen.getByRole('textbox'); + expect(input).toBeDisabled(); + }); + }); + describe('Optional MultiEntityPicker', () => { beforeEach(() => { uiSchema = { diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index bf2bb34ab6..bcc2e6e773 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -61,10 +61,12 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { formData, idSchema, } = props; + const catalogFilter = buildCatalogFilter(uiSchema); const defaultKind = uiSchema['ui:options']?.defaultKind; const defaultNamespace = uiSchema['ui:options']?.defaultNamespace || undefined; + const isDisabled = uiSchema?.['ui:disabled'] ?? false; const [noOfItemsSelected, setNoOfItemsSelected] = useState(0); const catalogApi = useApi(catalogApiRef); @@ -151,7 +153,10 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { multiple filterSelectedOptions disabled={ - required && !allowArbitraryValues && entities?.entities?.length === 1 + isDisabled || + (required && + !allowArbitraryValues && + entities?.entities?.length === 1) } id={idSchema?.$id} defaultValue={formData} @@ -175,6 +180,7 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { Date: Sun, 23 Feb 2025 23:02:08 -0400 Subject: [PATCH 04/21] feat: ui:disabled allowed in BitbucketRepoBranchPicker Signed-off-by: Nikunj Hudka --- .../BitbucketRepoBranchPicker.test.tsx | 27 ++++++++++++++++++- .../BitbucketRepoBranchPicker.tsx | 9 ++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.test.tsx index 40496a66af..f0d92d86ba 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.test.tsx @@ -20,7 +20,13 @@ import { scaffolderApiRef, } from '@backstage/plugin-scaffolder-react'; import { BitbucketRepoBranchPicker } from './BitbucketRepoBranchPicker'; -import { act, fireEvent, render, waitFor } from '@testing-library/react'; +import { + act, + fireEvent, + render, + waitFor, + screen, +} from '@testing-library/react'; import { TestApiProvider } from '@backstage/test-utils'; import userEvent from '@testing-library/user-event'; @@ -44,6 +50,25 @@ describe('BitbucketRepoBranchPicker', () => { expect(getByRole('textbox')).toHaveValue('main'); }); + it('input field disabled', () => { + render( + + + , + ); + + const input = screen.getByRole('textbox'); + + // Expect input to be disabled + expect(input).toBeDisabled(); + expect(input).toHaveValue('main'); + }); + it('calls onChange when the input field changes', () => { const onChange = jest.fn(); diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.tsx index 5777f95ed2..0e37c10567 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.tsx @@ -36,6 +36,7 @@ export const BitbucketRepoBranchPicker = ({ state, rawErrors, accessToken, + isDisabled, required, }: BaseRepoBranchPickerProps<{ accessToken?: string; @@ -86,9 +87,15 @@ export const BitbucketRepoBranchPicker = ({ onChange={(_, newValue) => { onChange({ branch: newValue || '' }); }} + disabled={isDisabled} options={availableBranches} renderInput={params => ( - + )} freeSolo autoSelect From 2be88bdcc08567370ee464c5de398a89d061e43b Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:02:38 -0400 Subject: [PATCH 05/21] feat: ui:disabled allowed in DefaultRepoBranchPicker Signed-off-by: Nikunj Hudka --- .../DefaultRepoBranchPicker.test.tsx | 19 ++++++++++++++++++- .../DefaultRepoBranchPicker.tsx | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.test.tsx index 77589e4478..952260ec81 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.test.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { DefaultRepoBranchPicker } from './DefaultRepoBranchPicker'; @@ -33,6 +33,23 @@ describe('DefaultRepoBranchPicker', () => { expect(getByRole('textbox')).toHaveValue('main'); }); + it('input field disabled', () => { + render( + , + ); + + const input = screen.getByRole('textbox'); + + // Expect input to be disabled + expect(input).toBeDisabled(); + expect(input).toHaveValue('main'); + }); + it('calls onChange when the input field changes', () => { const onChange = jest.fn(); diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx index 0fecfb7947..b06bdfa50c 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx @@ -32,6 +32,7 @@ export const DefaultRepoBranchPicker = ({ onChange, state, rawErrors, + isDisabled, required, }: BaseRepoBranchPickerProps) => { const { branch } = state; @@ -45,6 +46,7 @@ export const DefaultRepoBranchPicker = ({ onChange({ branch: e.target.value })} value={branch} /> From 3d9fadce140411d5e35cfcb1b2555ceebb2616ce Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:03:10 -0400 Subject: [PATCH 06/21] feat: ui:disabled allowed in GitHubRepoBranchPicker Signed-off-by: Nikunj Hudka --- .../GitHubRepoBranchPicker.test.tsx | 27 ++++++++++++++++++- .../GitHubRepoBranchPicker.tsx | 9 ++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.test.tsx index 3e89b13aa7..56c65e6cf7 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.test.tsx @@ -20,7 +20,13 @@ import { scaffolderApiRef, } from '@backstage/plugin-scaffolder-react'; import { GitHubRepoBranchPicker } from './GitHubRepoBranchPicker'; -import { act, fireEvent, render, waitFor } from '@testing-library/react'; +import { + act, + fireEvent, + render, + waitFor, + screen, +} from '@testing-library/react'; import { TestApiProvider } from '@backstage/test-utils'; import userEvent from '@testing-library/user-event'; @@ -44,6 +50,25 @@ describe('GitHubRepoBranchPicker', () => { expect(getByRole('textbox')).toHaveValue('main'); }); + it('input field disabled', () => { + render( + + + , + ); + + const input = screen.getByRole('textbox'); + + // Expect input to be disabled + expect(input).toBeDisabled(); + expect(input).toHaveValue('main'); + }); + it('calls onChange when the input field changes', () => { const onChange = jest.fn(); diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.tsx index e4995ceeaa..cfcc3ad900 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.tsx @@ -36,6 +36,7 @@ export const GitHubRepoBranchPicker = ({ state, rawErrors, accessToken, + isDisabled, required, }: BaseRepoBranchPickerProps<{ accessToken?: string; @@ -86,9 +87,15 @@ export const GitHubRepoBranchPicker = ({ onChange={(_, newValue) => { onChange({ branch: newValue || '' }); }} + disabled={isDisabled} options={availableBranches} renderInput={params => ( - + )} freeSolo autoSelect From d3fdf25f96699440a21a7214d1b603de7167edc3 Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:03:41 -0400 Subject: [PATCH 07/21] feat: ui:disabled allowed in RepoBranchPicker Signed-off-by: Nikunj Hudka --- .../RepoBranchPicker.test.tsx | 36 ++++++++++++++++++- .../RepoBranchPicker/RepoBranchPicker.tsx | 3 ++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.test.tsx index f9c23be407..7b97a836be 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.test.tsx @@ -31,7 +31,7 @@ import { useTemplateSecrets, ScaffolderRJSFField, } from '@backstage/plugin-scaffolder-react'; -import { act, fireEvent } from '@testing-library/react'; +import { act, fireEvent, screen } from '@testing-library/react'; import { RepoBranchPicker } from './RepoBranchPicker'; describe('RepoBranchPicker', () => { @@ -92,6 +92,40 @@ describe('RepoBranchPicker', () => { ); }); + it('should disable the picker when ui:disabled', async () => { + const onSubmit = jest.fn(); + + await renderInTestApp( + + +
, + }} + onSubmit={onSubmit} + formContext={{ + formData: { repoUrl: 'github.com' }, + }} + /> + + , + ); + + const input = screen.getByRole('textbox'); + + expect(input).toBeDisabled(); + }); + it('should render properly with title and description', async () => { const { getByText } = await renderInTestApp( { uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey && secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey] } + isDisabled={uiSchema?.['ui:disabled'] ?? false} required={required} /> ); @@ -146,6 +147,7 @@ export const RepoBranchPicker = (props: RepoBranchPickerProps) => { uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey && secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey] } + isDisabled={uiSchema?.['ui:disabled'] ?? false} required={required} /> ); @@ -155,6 +157,7 @@ export const RepoBranchPicker = (props: RepoBranchPickerProps) => { onChange={updateLocalState} state={state} rawErrors={rawErrors} + isDisabled={uiSchema?.['ui:disabled'] ?? false} required={required} /> ); From 3ff088b4469ab2ad8511dacafae9c5dabf311756 Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:04:28 -0400 Subject: [PATCH 08/21] feat: ui:disabled allowed in AzureRepoPicker Signed-off-by: Nikunj Hudka --- .../RepoUrlPicker/AzureRepoPicker.test.tsx | 17 +++++++++++++++++ .../fields/RepoUrlPicker/AzureRepoPicker.tsx | 7 +++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx index 904fb615eb..3db86a7512 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx @@ -30,6 +30,23 @@ describe('AzureRepoPicker', () => { expect(allInputs).toHaveLength(2); }); + it('disables input fields when isDisabled is true', async () => { + const { getAllByRole } = await renderInTestApp( + , + ); + + const allInputs = getAllByRole('textbox'); + + allInputs.forEach(input => { + expect(input).toBeDisabled(); + }); + }); + describe('org field', () => { it('calls onChange when the organisation changes', async () => { const onChange = jest.fn(); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx index 59e666482a..7f886db0bc 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx @@ -35,6 +35,7 @@ export const AzureRepoPicker = ( rawErrors, state, onChange, + isDisabled, } = props; const { t } = useTranslationRef(scaffolderTranslationRef); @@ -63,7 +64,7 @@ export const AzureRepoPicker = ( onChange={s => onChange({ organization: String(Array.isArray(s) ? s[0] : s) }) } - disabled={allowedOrganizations.length === 1} + disabled={isDisabled || allowedOrganizations.length === 1} selected={organization} items={organizationItems} /> @@ -77,6 +78,7 @@ export const AzureRepoPicker = ( label={t('fields.azureRepoPicker.organization.title')} onChange={e => onChange({ organization: e.target.value })} helperText={t('fields.azureRepoPicker.organization.description')} + disabled={isDisabled} value={organization} /> )} @@ -94,7 +96,7 @@ export const AzureRepoPicker = ( onChange={s => onChange({ project: String(Array.isArray(s) ? s[0] : s) }) } - disabled={allowedProject.length === 1} + disabled={isDisabled || allowedProject.length === 1} selected={project} items={projectItems} /> @@ -108,6 +110,7 @@ export const AzureRepoPicker = ( label={t('fields.azureRepoPicker.project.title')} onChange={e => onChange({ project: e.target.value })} value={project} + disabled={isDisabled} helperText={t('fields.azureRepoPicker.project.description')} /> )} From fe2c968a3b211c67156f803e53215bebfc0b6a08 Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:05:05 -0400 Subject: [PATCH 09/21] feat: ui:disabled allowed in BitbucketRepoPicker Signed-off-by: Nikunj Hudka --- .../BitbucketRepoPicker.test.tsx | 46 +++++++++++++++++++ .../RepoUrlPicker/BitbucketRepoPicker.tsx | 9 +++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx index af3952ff51..a39a660e72 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx @@ -271,4 +271,50 @@ describe('BitbucketRepoPicker', () => { ); }); }); + + describe('BitbucketRepoPicker - isDisabled', () => { + it('disables workspace and project inputs when isDisabled is true', async () => { + const { getAllByRole } = await renderInTestApp( + + + , + ); + + const inputs = getAllByRole('textbox'); + expect(inputs).toHaveLength(2); + expect(inputs[0]).toBeDisabled(); + expect(inputs[1]).toBeDisabled(); + }); + + it('does not disable workspace and project inputs when isDisabled is false', async () => { + const { getAllByRole } = await renderInTestApp( + + + , + ); + + const inputs = getAllByRole('textbox'); + expect(inputs).toHaveLength(2); + expect(inputs[0]).not.toBeDisabled(); + expect(inputs[1]).not.toBeDisabled(); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx index 45a6439a63..c8a73eee2d 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx @@ -49,6 +49,7 @@ export const BitbucketRepoPicker = ( rawErrors, state, accessToken, + isDisabled, } = props; const { t } = useTranslationRef(scaffolderTranslationRef); @@ -176,7 +177,7 @@ export const BitbucketRepoPicker = ( onChange={s => onChange({ workspace: String(Array.isArray(s) ? s[0] : s) }) } - disabled={allowedOwners.length === 1} + disabled={isDisabled || allowedOwners.length === 1} selected={workspace} items={ownerItems} /> @@ -191,9 +192,11 @@ export const BitbucketRepoPicker = ( )} + disabled={isDisabled} freeSolo autoSelect /> @@ -215,7 +218,7 @@ export const BitbucketRepoPicker = ( onChange={s => onChange({ project: String(Array.isArray(s) ? s[0] : s) }) } - disabled={allowedProjects.length === 1} + disabled={isDisabled || allowedProjects.length === 1} selected={project} items={projectItems} /> @@ -226,10 +229,12 @@ export const BitbucketRepoPicker = ( onChange({ project: newValue || '' }); }} options={availableProjects} + disabled={isDisabled} renderInput={params => ( )} From a986c02737254f6e0091df4a965854ea2615e5e9 Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:05:27 -0400 Subject: [PATCH 10/21] feat: ui:disabled allowed in GerritRepoPicker Signed-off-by: Nikunj Hudka --- .../RepoUrlPicker/GerritRepoPicker.test.tsx | 17 +++++++++++++++++ .../fields/RepoUrlPicker/GerritRepoPicker.tsx | 4 +++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx index f00e286418..54fb0e0e3f 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx @@ -20,6 +20,23 @@ import { fireEvent } from '@testing-library/react'; import { renderInTestApp } from '@backstage/test-utils'; describe('GerritRepoPicker', () => { + it('disables input fields when isDisabled is true', async () => { + const { getAllByRole } = await renderInTestApp( + , + ); + + const allInputs = getAllByRole('textbox'); + + allInputs.forEach(input => { + expect(input).toBeDisabled(); + }); + }); + describe('owner input field', () => { it('calls onChange when the owner input changes', async () => { const onChange = jest.fn(); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx index bcc665976f..20d601bf45 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx @@ -21,7 +21,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../../translation'; export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => { - const { onChange, rawErrors, state } = props; + const { onChange, rawErrors, state, isDisabled } = props; const { t } = useTranslationRef(scaffolderTranslationRef); const { workspace, owner } = state; return ( @@ -32,6 +32,7 @@ export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => { label={t('fields.gerritRepoPicker.owner.title')} onChange={e => onChange({ owner: e.target.value })} helperText={t('fields.gerritRepoPicker.owner.description')} + disabled={isDisabled} value={owner} /> @@ -44,6 +45,7 @@ export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => { id="parentInput" label={t('fields.gerritRepoPicker.parent.title')} onChange={e => onChange({ workspace: e.target.value })} + disabled={isDisabled} value={workspace} helperText={t('fields.gerritRepoPicker.parent.description')} /> From 9695c9e29bf5585bcaa55b2ed52bc1ab5ddef622 Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:06:04 -0400 Subject: [PATCH 11/21] feat: ui:disabled allowed in GiteaRepoPicker Signed-off-by: Nikunj Hudka --- .../RepoUrlPicker/GiteaRepoPicker.test.tsx | 16 ++++++++++++++++ .../fields/RepoUrlPicker/GiteaRepoPicker.tsx | 5 +++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx index 056d8f0d11..885e5e3e44 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx @@ -20,6 +20,22 @@ import { fireEvent } from '@testing-library/react'; import { renderInTestApp } from '@backstage/test-utils'; describe('GiteaRepoPicker', () => { + it('disables input fields when isDisabled is true', async () => { + const { getAllByRole } = await renderInTestApp( + , + ); + + const allInputs = getAllByRole('textbox'); + + allInputs.forEach(input => { + expect(input).toBeDisabled(); + }); + }); describe('owner input field', () => { it('calls onChange when the owner input changes', async () => { const onChange = jest.fn(); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx index 9478d61838..92433e0796 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx @@ -28,7 +28,7 @@ export const GiteaRepoPicker = ( allowedRepos?: string[]; }>, ) => { - const { allowedOwners = [], state, onChange, rawErrors } = props; + const { allowedOwners = [], state, onChange, rawErrors, isDisabled } = props; const { t } = useTranslationRef(scaffolderTranslationRef); const ownerItems: SelectItem[] = allowedOwners ? allowedOwners.map(i => ({ label: i, value: i })) @@ -55,7 +55,7 @@ export const GiteaRepoPicker = ( ), }) } - disabled={allowedOwners.length === 1} + disabled={isDisabled || allowedOwners.length === 1} selected={owner} items={ownerItems} /> @@ -70,6 +70,7 @@ export const GiteaRepoPicker = ( label={t('fields.giteaRepoPicker.owner.inputTitle')} onChange={e => onChange({ owner: e.target.value })} helperText={t('fields.giteaRepoPicker.owner.description')} + disabled={isDisabled} value={owner} /> From 6c840fe9c7cd7a356e2bfcf212198ed4f32f55cb Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:06:32 -0400 Subject: [PATCH 12/21] feat: ui:disabled allowed in GithubRepoPicker Signed-off-by: Nikunj Hudka --- .../RepoUrlPicker/GithubRepoPicker.test.tsx | 34 +++++++++++++++++++ .../fields/RepoUrlPicker/GithubRepoPicker.tsx | 13 +++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx index d5657606a7..92c6dc4090 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx @@ -170,4 +170,38 @@ describe('GithubRepoPicker', () => { ); }); }); + + describe('GithubRepoPicker - isDisabled', () => { + it('disables all inputs when isDisabled is true', async () => { + const { getByLabelText } = await renderInTestApp( + + + , + ); + + const ownerInput = getByLabelText(/owner/i); + expect(ownerInput).toBeDisabled(); + }); + + it('does not disable inputs when isDisabled is false', async () => { + const { getByLabelText } = await renderInTestApp( + + + , + ); + + const ownerInput = getByLabelText(/owner/i); + expect(ownerInput).not.toBeDisabled(); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx index 66a662b530..c1d675d0b6 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx @@ -34,7 +34,14 @@ export const GithubRepoPicker = ( accessToken?: string; }>, ) => { - const { allowedOwners = [], rawErrors, state, onChange, accessToken } = props; + const { + allowedOwners = [], + rawErrors, + state, + onChange, + accessToken, + isDisabled, + } = props; const { t } = useTranslationRef(scaffolderTranslationRef); const ownerItems: SelectItem[] = allowedOwners ? allowedOwners.map(i => ({ label: i, value: i })) @@ -110,7 +117,7 @@ export const GithubRepoPicker = ( onChange={s => onChange({ owner: String(Array.isArray(s) ? s[0] : s) }) } - disabled={allowedOwners.length === 1} + disabled={isDisabled || allowedOwners.length === 1} selected={owner} items={ownerItems} /> @@ -126,10 +133,12 @@ export const GithubRepoPicker = ( )} freeSolo + disabled={isDisabled} autoSelect /> )} From 222a014aa8501b7c0591edfc921da3889015a6aa Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:06:59 -0400 Subject: [PATCH 13/21] feat: ui:disabled allowed in GitlabRepoPicker Signed-off-by: Nikunj Hudka --- .../RepoUrlPicker/GitlabRepoPicker.test.tsx | 67 +++++++++++++++++++ .../fields/RepoUrlPicker/GitlabRepoPicker.tsx | 13 +++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx index a8fb42cae3..8a1604dfee 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx @@ -31,6 +31,73 @@ describe('GitlabRepoPicker', () => { }), ), }; + + describe('GitlabRepoPicker - isDisabled', () => { + it('disables owner input when isDisabled is true', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + expect(getByRole('textbox')).toBeDisabled(); + }); + + it('does not disable owner input when isDisabled is false', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + expect(getByRole('textbox')).not.toBeDisabled(); + }); + + it('disables select input when allowedOwners are provided and isDisabled is true', async () => { + const allowedOwners = ['owner1', 'owner2']; + const { getByRole } = await renderInTestApp( + + + , + ); + + expect(getByRole('combobox')).toBeDisabled(); + }); + + it('does not disable select input when allowedOwners are provided and isDisabled is false', async () => { + const allowedOwners = ['owner1', 'owner2']; + const { getByRole } = await renderInTestApp( + + + , + ); + + expect(getByRole('combobox')).not.toBeDisabled(); + }); + }); + describe('owner field', () => { it('renders a select if there is a list of allowed owners', async () => { const allowedOwners = ['owner1', 'owner2']; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx index d97c9431ee..0d7c8b365a 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx @@ -33,7 +33,14 @@ export const GitlabRepoPicker = ( accessToken?: string; }>, ) => { - const { allowedOwners = [], state, onChange, rawErrors, accessToken } = props; + const { + allowedOwners = [], + state, + onChange, + rawErrors, + accessToken, + isDisabled, + } = props; const [availableGroups, setAvailableGroups] = useState< { title: string; id: string }[] >([]); @@ -128,7 +135,7 @@ export const GitlabRepoPicker = ( ), }) } - disabled={allowedOwners.length === 1} + disabled={isDisabled || allowedOwners.length === 1} selected={owner} items={ownerItems} /> @@ -147,10 +154,12 @@ export const GitlabRepoPicker = ( )} freeSolo + disabled={isDisabled} autoSelect /> )} From cd6d2320104dab80b0a74305b683e88af0ef62fd Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:07:27 -0400 Subject: [PATCH 14/21] feat: ui:disabled allowed in RepoUrlPicker Signed-off-by: Nikunj Hudka --- .../RepoUrlPicker/RepoUrlPicker.test.tsx | 30 +++++++++++++++++++ .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 13 +++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index 163f397857..2d612f317d 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -119,6 +119,36 @@ describe('RepoUrlPicker', () => { ); }); + it('should disable the picker when ui:disabled', async () => { + const onSubmit = jest.fn(); + const { getAllByRole } = await renderInTestApp( + + + , + }} + onSubmit={onSubmit} + /> + + , + ); + + const [ownerInput, repoInput] = getAllByRole('textbox'); + + expect(ownerInput).toBeDisabled(); + expect(repoInput).toBeDisabled(); + }); + it('should render properly with allowedHosts', async () => { const { getByRole } = await renderInTestApp( uiSchema?.['ui:options']?.allowedRepos ?? [], [uiSchema], ); - + const isDisabled = useMemo( + () => uiSchema?.['ui:disabled'] ?? false, + [uiSchema], + ); const { owner, organization, project, repoName } = state; useEffect(() => { @@ -179,6 +182,7 @@ export const RepoUrlPicker = ( hosts={allowedHosts} onChange={host => setState(prevState => ({ ...prevState, host }))} rawErrors={rawErrors} + isDisabled={isDisabled} /> {hostType === 'github' && ( )} @@ -207,6 +213,7 @@ export const RepoUrlPicker = ( rawErrors={rawErrors} state={state} onChange={updateLocalState} + isDisabled={isDisabled} accessToken={ uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey && secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey] @@ -220,6 +227,7 @@ export const RepoUrlPicker = ( rawErrors={rawErrors} state={state} onChange={updateLocalState} + isDisabled={isDisabled} accessToken={ uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey && secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey] @@ -232,6 +240,7 @@ export const RepoUrlPicker = ( allowedProject={allowedProjects} rawErrors={rawErrors} state={state} + isDisabled={isDisabled} onChange={updateLocalState} /> )} @@ -240,6 +249,7 @@ export const RepoUrlPicker = ( rawErrors={rawErrors} state={state} onChange={updateLocalState} + isDisabled={isDisabled} /> )} From c5d2049c5437f6071c1965990c8fa7f382d1c3c4 Mon Sep 17 00:00:00 2001 From: Nikunj Hudka Date: Sun, 23 Feb 2025 23:08:04 -0400 Subject: [PATCH 15/21] feat: ui:disabled allowed in RepoUrlPickerHost Signed-off-by: Nikunj Hudka --- .../RepoUrlPicker/RepoUrlPickerHost.test.tsx | 27 +++++++++++++++++++ .../RepoUrlPicker/RepoUrlPickerHost.tsx | 5 ++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx index a070e0c6e4..0f85b6d037 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx @@ -100,4 +100,31 @@ describe('RepoUrlPickerHostField', () => { expect(listbox.getAllByRole('option')).toHaveLength(2); }); + + it('disables the host select when isDisabled is true', async () => { + const mockOnChange = jest.fn(); + const mockScaffolderApi = { + getIntegrationsList: jest.fn().mockResolvedValue({ + integrations: [ + { host: 'github.com', title: 'github.com', type: 'github' }, + { host: 'gitlab.com', title: 'gitlab.com', type: 'gitlab' }, + ], + }), + }; + + const { getByTestId } = await renderInTestApp( + + + , + ); + + const selectElement = getByTestId('host-select').querySelector('select'); + + expect(selectElement).toBeDisabled(); + }); }); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx index 90f8eaf449..787aa68987 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx @@ -28,8 +28,9 @@ export const RepoUrlPickerHost = (props: { hosts?: string[]; onChange: (host: string) => void; rawErrors: string[]; + isDisabled?: boolean; }) => { - const { host, hosts, onChange, rawErrors } = props; + const { host, hosts, onChange, rawErrors, isDisabled } = props; const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderApi = useApi(scaffolderApiRef); @@ -75,7 +76,7 @@ export const RepoUrlPickerHost = (props: { >