From 1d8f39a749611aace0a5b0c31ef8e6919aa6415b Mon Sep 17 00:00:00 2001 From: Oleksii Kurinnyi Date: Wed, 10 Jan 2024 12:30:03 +0200 Subject: [PATCH] feat: improved the gitconfig page Signed-off-by: Oleksii Kurinnyi --- .../InputGroupExtended/__mocks__/index.tsx | 9 +- .../__snapshots__/index.spec.tsx.snap | 35 ++-- .../__tests__/index.spec.tsx | 158 +++++++----------- .../components/InputGroupExtended/index.tsx | 89 +++------- .../SectionUser/Email/__mocks__/index.tsx | 11 +- .../__snapshots__/index.spec.tsx.snap | 91 ++++++++-- .../Email/__tests__/index.spec.tsx | 46 +++-- .../{ => Form}/SectionUser/Email/index.tsx | 53 +++--- .../SectionUser/Name/__mocks__/index.tsx | 11 +- .../__snapshots__/index.spec.tsx.snap | 78 +++++++-- .../SectionUser/Name/__tests__/index.spec.tsx | 47 +++--- .../{ => Form}/SectionUser/Name/index.tsx | 50 +++--- .../SectionUser/__mocks__/index.tsx | 10 +- .../__snapshots__/index.spec.tsx.snap | 78 +++++++++ .../SectionUser/__tests__/index.spec.tsx | 55 ++++-- .../GitConfig/Form/SectionUser/index.tsx | 65 +++++++ .../GitConfig/Form/__mocks__/index.tsx | 32 ++++ .../__snapshots__/index.spec.tsx.snap | 66 ++++++++ .../GitConfig/Form/__tests__/index.spec.tsx | 135 +++++++++++++++ .../UserPreferences/GitConfig/Form/index.tsx | 112 +++++++++++++ .../__snapshots__/index.spec.tsx.snap | 50 ------ .../GitConfig/SectionUser/index.tsx | 58 ------- .../__snapshots__/index.spec.tsx.snap | 89 +++++----- .../GitConfig/__tests__/index.spec.tsx | 22 ++- .../pages/UserPreferences/GitConfig/index.tsx | 60 ++++--- .../store/GitConfig/__tests__/index.spec.ts | 13 +- .../GitConfig/__tests__/selectors.spec.ts | 10 +- .../src/store/GitConfig/index.ts | 10 +- .../src/store/GitConfig/selectors.ts | 4 +- .../src/store/GitConfig/types.ts | 2 +- 30 files changed, 1023 insertions(+), 526 deletions(-) rename packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/{ => Form}/SectionUser/Email/__mocks__/index.tsx (55%) rename packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/{ => Form}/SectionUser/Email/__tests__/__snapshots__/index.spec.tsx.snap (52%) rename packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/{ => Form}/SectionUser/Email/__tests__/index.spec.tsx (58%) rename packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/{ => Form}/SectionUser/Email/index.tsx (73%) rename packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/{ => Form}/SectionUser/Name/__mocks__/index.tsx (55%) rename packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/{ => Form}/SectionUser/Name/__tests__/__snapshots__/index.spec.tsx.snap (57%) rename packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/{ => Form}/SectionUser/Name/__tests__/index.spec.tsx (53%) rename packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/{ => Form}/SectionUser/Name/index.tsx (71%) rename packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/{ => Form}/SectionUser/__mocks__/index.tsx (69%) create mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/__tests__/__snapshots__/index.spec.tsx.snap rename packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/{ => Form}/SectionUser/__tests__/index.spec.tsx (59%) create mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/index.tsx create mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__mocks__/index.tsx create mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__tests__/__snapshots__/index.spec.tsx.snap create mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__tests__/index.spec.tsx create mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/index.tsx delete mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/__tests__/__snapshots__/index.spec.tsx.snap delete mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/index.tsx diff --git a/packages/dashboard-frontend/src/components/InputGroupExtended/__mocks__/index.tsx b/packages/dashboard-frontend/src/components/InputGroupExtended/__mocks__/index.tsx index 21a01b4a5..bae2395e2 100644 --- a/packages/dashboard-frontend/src/components/InputGroupExtended/__mocks__/index.tsx +++ b/packages/dashboard-frontend/src/components/InputGroupExtended/__mocks__/index.tsx @@ -13,17 +13,16 @@ import { Button } from '@patternfly/react-core'; import * as React from 'react'; -import { Props, State } from '..'; +import { Props } from '..'; -export class InputGroupExtended extends React.PureComponent { +export class InputGroupExtended extends React.PureComponent { render(): React.ReactElement { - const { children, onCancel, onSave, validated } = this.props; + const { children, onRemove, validated } = this.props; return (
{children} {validated} -
); } diff --git a/packages/dashboard-frontend/src/components/InputGroupExtended/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/components/InputGroupExtended/__tests__/__snapshots__/index.spec.tsx.snap index 022f50d2a..969a014aa 100644 --- a/packages/dashboard-frontend/src/components/InputGroupExtended/__tests__/__snapshots__/index.spec.tsx.snap +++ b/packages/dashboard-frontend/src/components/InputGroupExtended/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,21 +1,32 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`InputGroupExtended snapshots editable 1`] = ` +exports[`InputGroupExtended snapshots readonly 1`] = ` value + +`; + +exports[`InputGroupExtended snapshots required 1`] = ` +
+ - -`; - -exports[`InputGroupExtended snapshots readonly 1`] = ` - - value - +
`; diff --git a/packages/dashboard-frontend/src/components/InputGroupExtended/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/InputGroupExtended/__tests__/index.spec.tsx index ca80e4760..63a04e41d 100644 --- a/packages/dashboard-frontend/src/components/InputGroupExtended/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/InputGroupExtended/__tests__/index.spec.tsx @@ -11,17 +11,15 @@ */ import { ValidatedOptions } from '@patternfly/react-core'; -import { StateMock } from '@react-mock/state'; import * as React from 'react'; -import getComponentRenderer, { screen } from '@/services/__mocks__/getComponentRenderer'; +import getComponentRenderer, { fireEvent, screen } from '@/services/__mocks__/getComponentRenderer'; -import { InputGroupExtended, Props, State } from '..'; +import { InputGroupExtended, Props } from '..'; const { createSnapshot, renderComponent } = getComponentRenderer(getComponent); -const mockOnCancel = jest.fn(); -const mockOnSave = jest.fn(); +const mockOnRemove = jest.fn(); describe('InputGroupExtended', () => { afterEach(() => { @@ -31,131 +29,97 @@ describe('InputGroupExtended', () => { describe('snapshots', () => { test('readonly', () => { const snapshot = createSnapshot({ + isLoading: false, readonly: true, + required: false, value: 'value', - onCancel: mockOnCancel, - onSave: mockOnSave, + onRemove: mockOnRemove, }); expect(snapshot.toJSON()).toMatchSnapshot(); }); - test('editable', () => { + test('required', () => { const snapshot = createSnapshot({ + isLoading: false, readonly: false, + required: true, value: 'value', - onCancel: mockOnCancel, - onSave: mockOnSave, + onRemove: mockOnRemove, }); expect(snapshot.toJSON()).toMatchSnapshot(); }); }); - it('should switch in edit mode', () => { + it('should handle submiting', () => { renderComponent({ + isLoading: false, readonly: false, + required: false, value: 'value', - onCancel: mockOnCancel, - onSave: mockOnSave, + validated: ValidatedOptions.success, + onRemove: mockOnRemove, }); - const editButton = screen.queryByTestId('button-edit'); - expect(editButton).not.toBeNull(); + const input = screen.queryByTestId('text-input'); + fireEvent.submit(input!); + }); - editButton!.click(); + it('should handle removing the entry', () => { + renderComponent({ + isLoading: false, + readonly: false, + required: false, + value: 'value', + validated: ValidatedOptions.success, + onRemove: mockOnRemove, + }); - expect(screen.queryByTestId('button-edit')).toBeNull(); + const removeButton = screen.queryByTestId('button-remove'); + expect(removeButton).not.toBeNull(); + expect(removeButton).toBeEnabled(); - expect(screen.queryByTestId('text-input')).not.toBeNull(); - expect(screen.queryByTestId('button-save')).not.toBeNull(); - expect(screen.queryByTestId('button-cancel')).not.toBeNull(); - }); + removeButton!.click(); - it('should switch out of edit mode', () => { - renderComponent( - { - readonly: false, - value: 'value', - onCancel: mockOnCancel, - onSave: mockOnSave, - }, - { - isEditMode: true, - }, - ); - - const cancelButton = screen.queryByTestId('button-cancel'); - expect(cancelButton).not.toBeNull(); - expect(cancelButton).toBeEnabled(); - - cancelButton!.click(); - - expect(mockOnCancel).toBeCalled(); - expect(screen.queryByTestId('button-edit')).not.toBeNull(); - - expect(screen.queryByTestId('text-input')).toBeNull(); - expect(screen.queryByTestId('button-save')).toBeNull(); - expect(screen.queryByTestId('button-cancel')).toBeNull(); + expect(mockOnRemove).toHaveBeenCalled(); }); - it('should handle save', () => { - renderComponent( - { - readonly: false, - value: 'value', - onCancel: mockOnCancel, - onSave: mockOnSave, - validated: ValidatedOptions.success, - }, - { - isEditMode: true, - }, - ); - - const saveButton = screen.queryByTestId('button-save'); - expect(saveButton).not.toBeNull(); - expect(saveButton).toBeEnabled(); - - saveButton!.click(); - - expect(mockOnSave).toBeCalled(); - expect(screen.queryByTestId('button-edit')).not.toBeNull(); - - expect(screen.queryByTestId('text-input')).toBeNull(); - expect(screen.queryByTestId('button-save')).toBeNull(); - expect(screen.queryByTestId('button-cancel')).toBeNull(); + it('should disable the remove button if required', () => { + renderComponent({ + isLoading: false, + readonly: false, + required: true, + value: 'value', + validated: ValidatedOptions.default, + onRemove: mockOnRemove, + }); + + const removeButton = screen.queryByTestId('button-remove'); + expect(removeButton).not.toBeNull(); + expect(removeButton).toBeDisabled(); }); - it('should disable save button if not validated', () => { - renderComponent( - { - readonly: false, - value: 'value', - onCancel: mockOnCancel, - onSave: mockOnSave, - validated: ValidatedOptions.error, - }, - { - isEditMode: true, - }, - ); - - const saveButton = screen.queryByTestId('button-save'); - expect(saveButton).not.toBeNull(); - expect(saveButton).toBeDisabled(); + it('should disable the remove button if in progress', () => { + renderComponent({ + isLoading: true, + readonly: false, + required: false, + value: 'value', + validated: ValidatedOptions.default, + onRemove: mockOnRemove, + }); + + const removeButton = screen.queryByTestId('button-remove'); + expect(removeButton).not.toBeNull(); + expect(removeButton).toBeDisabled(); }); }); -function getComponent(props: Props, localState?: Partial): React.ReactElement { - const component = ( +function getComponent(props: Props): React.ReactElement { + return ( -
+ ); - if (localState) { - return {component}; - } else { - return component; - } } diff --git a/packages/dashboard-frontend/src/components/InputGroupExtended/index.tsx b/packages/dashboard-frontend/src/components/InputGroupExtended/index.tsx index c8f1bb733..2ecddfb58 100644 --- a/packages/dashboard-frontend/src/components/InputGroupExtended/index.tsx +++ b/packages/dashboard-frontend/src/components/InputGroupExtended/index.tsx @@ -10,97 +10,50 @@ * Red Hat, Inc. - initial API and implementation */ -import { Button, Form, InputGroup, ValidatedOptions } from '@patternfly/react-core'; -import { CheckIcon, PencilAltIcon, TimesIcon } from '@patternfly/react-icons'; +import { Button, InputGroup, ValidatedOptions } from '@patternfly/react-core'; +import { MinusCircleIcon } from '@patternfly/react-icons'; import * as React from 'react'; import styles from '@/components/InputGroupExtended/index.module.css'; export type Props = React.PropsWithChildren & { + isLoading: boolean; readonly: boolean; + required: boolean; validated?: ValidatedOptions; value: string; - onCancel: () => void; - onSave: () => void; + onRemove: (() => void) | undefined; }; -export type State = { - isEditMode: boolean; -}; - -export class InputGroupExtended extends React.PureComponent { - constructor(props: Props) { - super(props); - - this.state = { - isEditMode: false, - }; - } - - private handleEdit(): void { - this.setState({ isEditMode: true }); - } - - private handleSave(): void { - this.setState({ isEditMode: false }); - this.props.onSave(); - } - - private handleCancel(): void { - this.setState({ isEditMode: false }); - this.props.onCancel(); - } - - private handleSubmit(e: React.FormEvent): void { - e.preventDefault(); - - if (this.canSave()) { - this.handleSave(); - } - } - private canSave(): boolean { - const { validated } = this.props; - return validated === ValidatedOptions.success; +export class InputGroupExtended extends React.PureComponent { + private handleRemove(): void { + this.props.onRemove?.(); } public render(): React.ReactElement { - const { children, readonly, value } = this.props; - const { isEditMode } = this.state; + const { children, isLoading, readonly, required, value, onRemove } = this.props; if (readonly) { return {value}; } - if (isEditMode === false) { - return ( - - {value} - - - ); - } - - const isSaveButtonDisabled = this.canSave() === false; + const isRemoveDisabled = required || isLoading; + const canRemove = onRemove !== undefined; return ( -
this.handleSubmit(e)}> - - {children} + + {children} + {canRemove && ( - - - + )} +
); } } diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/__mocks__/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/__mocks__/index.tsx similarity index 55% rename from packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/__mocks__/index.tsx rename to packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/__mocks__/index.tsx index d3c06e129..8d778d70c 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/__mocks__/index.tsx +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/__mocks__/index.tsx @@ -16,8 +16,15 @@ import { Props } from '..'; export class GitConfigUserEmail extends React.PureComponent { public render(): React.ReactElement { - const { onChange } = this.props; + const { onChange, isLoading, value } = this.props; - return ; + return ( +
+ {isLoading} + {value} + + +
+ ); } } diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/__tests__/__snapshots__/index.spec.tsx.snap similarity index 52% rename from packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/__tests__/__snapshots__/index.spec.tsx.snap rename to packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/__tests__/__snapshots__/index.spec.tsx.snap index 17580d5b0..e909a3c32 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/__tests__/__snapshots__/index.spec.tsx.snap +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/__tests__/__snapshots__/index.spec.tsx.snap @@ -31,13 +31,13 @@ exports[`GitConfigUserEmail snapshot 1`] = ` >
+ > + error +
+
+ The value is not a valid email address. +
+
+ +`; + +exports[`GitConfigUserEmail snapshot, not loading 1`] = ` +
+
+ + +
+
+
+ + + error +
- +
+ The value is not a valid email address. +
`; diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/__tests__/index.spec.tsx similarity index 58% rename from packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/__tests__/index.spec.tsx rename to packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/__tests__/index.spec.tsx index cc8545835..87d1e5918 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/__tests__/index.spec.tsx @@ -18,7 +18,7 @@ import getComponentRenderer, { screen } from '@/services/__mocks__/getComponentR import { GitConfigUserEmail } from '..'; -jest.mock('../../../../../../components/InputGroupExtended'); +jest.mock('@/components/InputGroupExtended'); const { createSnapshot, renderComponent } = getComponentRenderer(getComponent); @@ -29,13 +29,17 @@ describe('GitConfigUserEmail', () => { jest.clearAllMocks(); }); + test('snapshot, not loading', () => { + const snapshot = createSnapshot('user@che', false); + expect(snapshot.toJSON()).toMatchSnapshot(); + }); test('snapshot', () => { - const snapshot = createSnapshot('user@che'); + const snapshot = createSnapshot('user@che', true); expect(snapshot.toJSON()).toMatchSnapshot(); }); it('should fail validation if value is empty', () => { - renderComponent('user@che.org'); + renderComponent('user@che.org', false); const textInput = screen.getByRole('textbox'); userEvent.clear(textInput); @@ -44,49 +48,43 @@ describe('GitConfigUserEmail', () => { }); it('should fail validation if value is too long', () => { - renderComponent('user@che.org'); + renderComponent('user@che.org', false); const textInput = screen.getByRole('textbox'); - userEvent.type(textInput, 'a'.repeat(129)); + userEvent.paste(textInput, 'a'.repeat(129)); expect(screen.getByTestId('validated')).toHaveTextContent(ValidatedOptions.error); }); it('should fail validation if is not a valid email', () => { - renderComponent('user@che.org'); + renderComponent('user@che.org', false); const textInput = screen.getByRole('textbox'); - userEvent.type(textInput, '@test'); + userEvent.paste(textInput, '@test'); expect(screen.getByTestId('validated')).toHaveTextContent(ValidatedOptions.error); }); - it('should handle save', () => { - renderComponent('user@che.org'); + it('should re-validate on component update', () => { + const { reRenderComponent } = renderComponent('', false); - const textInput = screen.getByRole('textbox'); - userEvent.type(textInput, 'a'); + expect(screen.getByTestId('validated')).toHaveTextContent(ValidatedOptions.error); - const buttonSave = screen.getByTestId('button-save'); - userEvent.click(buttonSave); + reRenderComponent('user@che.com', false); - expect(mockOnChange).toBeCalledWith('user@che.orga'); + expect(screen.getByTestId('validated')).toHaveTextContent(ValidatedOptions.default); }); - it('should handle cancel', () => { - renderComponent('user@che.org'); + it('should handle value changing', () => { + renderComponent('user@che.org', false); const textInput = screen.getByRole('textbox'); - userEvent.type(textInput, 'a'); - - const buttonCancel = screen.getByTestId('button-cancel'); - userEvent.click(buttonCancel); + userEvent.paste(textInput, 'a'); - expect(textInput).toHaveValue('user@che.org'); - expect(mockOnChange).not.toBeCalled(); + expect(mockOnChange).toHaveBeenCalledWith('user@che.orga', true); }); }); -function getComponent(value: string): React.ReactElement { - return ; +function getComponent(value: string, isLoading: boolean): React.ReactElement { + return ; } diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/index.tsx similarity index 73% rename from packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/index.tsx rename to packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/index.tsx index 45c7a4b19..4e220fd71 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Email/index.tsx +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Email/index.tsx @@ -23,12 +23,12 @@ const REGEX = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/; const ERROR_INVALID_EMAIL = 'The value is not a valid email address.'; export type Props = { + isLoading: boolean; value: string; - onChange: (value: string) => void; + onChange: (value: string, isValid: boolean) => void; }; export type State = { errorMessage?: string; - value: string; validated: ValidatedOptions | undefined; }; @@ -37,59 +37,59 @@ export class GitConfigUserEmail extends React.PureComponent { super(props); this.state = { - value: props.value, validated: undefined, }; } - private handleChange(value: string): void { - this.validate(value); - this.setState({ value }); + public componentDidMount(): void { + const { value } = this.props; + this.validate(value, true); } - private handleSave(): void { - const { value } = this.state; - this.props.onChange(value); + public componentDidUpdate(prevProps: Readonly): void { + const { value } = this.props; + if (value !== prevProps.value) { + this.validate(value, true); + } } - private handleCancel(): void { - const { value } = this.props; - this.setState({ - value, - validated: undefined, - }); + private handleChange(value: string): void { + const isValid = this.validate(value); + this.props.onChange(value, isValid); } - private validate(value: string): void { + private validate(value: string, initial = false): boolean { if (value.length === 0) { this.setState({ errorMessage: ERROR_REQUIRED_VALUE, validated: ValidatedOptions.error, }); - return; + return false; } if (value.length > MAX_LENGTH) { this.setState({ errorMessage: ERROR_MAX_LENGTH, validated: ValidatedOptions.error, }); - return; + return false; } if (!REGEX.test(value)) { this.setState({ errorMessage: ERROR_INVALID_EMAIL, validated: ValidatedOptions.error, }); - return; + return false; } this.setState({ errorMessage: undefined, - validated: ValidatedOptions.success, + validated: initial === true ? ValidatedOptions.default : ValidatedOptions.success, }); + return true; } public render(): React.ReactElement { - const { errorMessage, value, validated } = this.state; + const { isLoading, value } = this.props; + const { errorMessage, validated } = this.state; const fieldId = 'gitconfig-user-email'; @@ -100,18 +100,21 @@ export class GitConfigUserEmail extends React.PureComponent { isRequired helperTextInvalid={errorMessage} helperTextIcon={} + validated={validated} > this.handleSave()} - onCancel={() => this.handleCancel()} + value={value} + onRemove={undefined} > this.handleChange(value)} /> diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Name/__mocks__/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Name/__mocks__/index.tsx similarity index 55% rename from packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Name/__mocks__/index.tsx rename to packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Name/__mocks__/index.tsx index 855ba546b..06dc94499 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Name/__mocks__/index.tsx +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Name/__mocks__/index.tsx @@ -16,8 +16,15 @@ import { Props } from '..'; export class GitConfigUserName extends React.PureComponent { public render(): React.ReactElement { - const { onChange } = this.props; + const { onChange, isLoading, value } = this.props; - return ; + return ( +
+ {isLoading} + {value} + + +
+ ); } } diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Name/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Name/__tests__/__snapshots__/index.spec.tsx.snap similarity index 57% rename from packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Name/__tests__/__snapshots__/index.spec.tsx.snap rename to packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Name/__tests__/__snapshots__/index.spec.tsx.snap index 613ca80c5..9fd6d9abe 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/Name/__tests__/__snapshots__/index.spec.tsx.snap +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/Name/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GitConfigUserName snapshot 1`] = ` +exports[`GitConfigUserName snapshot, loading 1`] = `
@@ -34,45 +34,101 @@ exports[`GitConfigUserName snapshot 1`] = ` aria-invalid={false} aria-label={null} className="pf-c-form-control" - data-ouia-component-id="OUIA-Generated-TextInputBase-1" + data-ouia-component-id="OUIA-Generated-TextInputBase-2" data-ouia-component-type="PF4/TextInput" data-ouia-safe={true} - disabled={false} + disabled={true} id="gitconfig-user-name" onBlur={[Function]} onChange={[Function]} onFocus={[Function]} - onSubmit={[Function]} required={false} type="text" value="user one" /> + > + default +
+ + + +`; + +exports[`GitConfigUserName snapshot, not loading 1`] = ` +
+
+ + +
+
+
+ + + default +
); diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/__tests__/__snapshots__/index.spec.tsx.snap new file mode 100644 index 000000000..d5f5c7747 --- /dev/null +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/__tests__/__snapshots__/index.spec.tsx.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GitConfigSectionUser snapshot 1`] = ` +
+
+
+
+
+ [user] +
+
+ + + user + + + +
+
+ + + user@che + + + +
+
+
+
+
+`; diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/__tests__/index.spec.tsx similarity index 59% rename from packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/__tests__/index.spec.tsx rename to packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/__tests__/index.spec.tsx index 0a53d4c8e..646a2ceec 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/__tests__/index.spec.tsx @@ -16,8 +16,8 @@ import getComponentRenderer, { screen } from '@/services/__mocks__/getComponentR import { GitConfigSectionUser, Props } from '..'; -jest.mock('../Email'); -jest.mock('../Name'); +jest.mock('@/pages/UserPreferences/GitConfig/Form/SectionUser/Email'); +jest.mock('@/pages/UserPreferences/GitConfig/Form/SectionUser/Name'); const mockOnChange = jest.fn(); @@ -31,9 +31,12 @@ describe('GitConfigSectionUser', () => { test('snapshot', () => { const snapshot = createSnapshot({ config: { - name: 'user', - email: 'user@che', + user: { + name: 'user', + email: 'user@che', + }, }, + isLoading: false, onChange: mockOnChange, }); expect(snapshot.toJSON()).toMatchSnapshot(); @@ -42,35 +45,51 @@ describe('GitConfigSectionUser', () => { it('should handle name change', () => { renderComponent({ config: { - name: 'user', - email: 'user@che', + user: { + name: 'user', + email: 'user@che', + }, }, + isLoading: false, onChange: mockOnChange, }); - screen.getByText('Change Name').click(); + screen.getByText('Change Name Valid').click(); - expect(mockOnChange).toHaveBeenCalledWith({ - name: 'new user', - email: 'user@che', - }); + expect(mockOnChange).toHaveBeenCalledWith( + { + user: { + name: 'new user', + email: 'user@che', + }, + }, + true, + ); }); it('should handle email change', () => { renderComponent({ config: { - name: 'user', - email: 'user@che', + user: { + name: 'user', + email: 'user@che', + }, }, + isLoading: false, onChange: mockOnChange, }); - screen.getByText('Change Email').click(); + screen.getByText('Change Email Valid').click(); - expect(mockOnChange).toHaveBeenCalledWith({ - name: 'user', - email: 'new-user@che', - }); + expect(mockOnChange).toHaveBeenCalledWith( + { + user: { + name: 'user', + email: 'new-user@che', + }, + }, + true, + ); }); }); diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/index.tsx new file mode 100644 index 000000000..b10974ea3 --- /dev/null +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/SectionUser/index.tsx @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { FormSection, Panel, PanelMain, PanelMainBody } from '@patternfly/react-core'; +import * as React from 'react'; + +import { GitConfigUserEmail } from '@/pages/UserPreferences/GitConfig/Form/SectionUser/Email'; +import { GitConfigUserName } from '@/pages/UserPreferences/GitConfig/Form/SectionUser/Name'; +import { GitConfig } from '@/store/GitConfig'; + +export type Props = { + config: GitConfig; + isLoading: boolean; + onChange: (gitConfig: GitConfig, isValid: boolean) => void; +}; + +export class GitConfigSectionUser extends React.PureComponent { + private handleChange(partialConfigUser: Partial, isValid: boolean): void { + const { config, onChange } = this.props; + + onChange( + { + ...config, + user: { + ...config.user, + ...partialConfigUser, + }, + }, + isValid, + ); + } + + public render(): React.ReactElement { + const { config, isLoading } = this.props; + return ( + + + + + this.handleChange({ name }, isValid)} + /> + this.handleChange({ email }, isValid)} + /> + + + + + ); + } +} diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__mocks__/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__mocks__/index.tsx new file mode 100644 index 000000000..f487c3bbd --- /dev/null +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__mocks__/index.tsx @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import React from 'react'; + +import { Props } from '..'; + +export class GitConfigForm extends React.PureComponent { + render(): React.ReactElement { + const { isLoading, gitConfig, onReload, onSave } = this.props; + + return ( +
+ {isLoading} + {JSON.stringify(gitConfig)} + + +
+ ); + } +} diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__tests__/__snapshots__/index.spec.tsx.snap new file mode 100644 index 000000000..9c6dd4c34 --- /dev/null +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__tests__/__snapshots__/index.spec.tsx.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GitConfigForm snapshot 1`] = ` +
+
+
+
+ test@che +
+
+ test +
+ +
+
+ + +
+
+
+`; diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__tests__/index.spec.tsx new file mode 100644 index 000000000..400b66852 --- /dev/null +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/__tests__/index.spec.tsx @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2018-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { StateMock } from '@react-mock/state'; +import React from 'react'; + +import getComponentRenderer, { screen, waitFor } from '@/services/__mocks__/getComponentRenderer'; + +import { GitConfigForm, Props, State } from '..'; + +jest.mock('@/pages/UserPreferences/GitConfig/Form/SectionUser'); + +const { createSnapshot, renderComponent } = getComponentRenderer(getComponent); + +const mockOnSave = jest.fn(); +const mockOnReload = jest.fn(); + +const defaultProps: Props = { + isLoading: false, + gitConfig: { + user: { + email: 'test@che', + name: 'test', + }, + }, + onSave: mockOnSave, + onReload: mockOnReload, +}; +const defaultState: State = { + isValid: true, + nextGitConfig: undefined, +}; + +describe('GitConfigForm', () => { + test('snapshot', () => { + const snapshot = createSnapshot(); + expect(snapshot.toJSON()).toMatchSnapshot(); + }); + + describe('buttons state', () => { + test('initial', () => { + renderComponent(); + + expect(screen.getByRole('button', { name: 'Save' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Reload' })).toBeEnabled(); + }); + + test('while loading data', () => { + renderComponent({ isLoading: true }); + + expect(screen.getByRole('button', { name: 'Save' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Reload' })).toBeDisabled(); + }); + + test('with valid changes', () => { + renderComponent( + {}, + { isValid: true, nextGitConfig: { user: { name: 'new name', email: 'test@che' } } }, + ); + + expect(screen.getByRole('button', { name: 'Save' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Reload' })).toBeEnabled(); + }); + + test('with invalid changes', () => { + renderComponent( + {}, + { isValid: false, nextGitConfig: { user: { name: '', email: 'test@che' } } }, + ); + + expect(screen.getByRole('button', { name: 'Save' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Reload' })).toBeEnabled(); + }); + + test('handle valid changes', () => { + renderComponent(); + + // state with no changes + expect(screen.getByRole('button', { name: 'Save' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Reload' })).toBeEnabled(); + + screen.getByRole('button', { name: 'Change Email Valid' }).click(); + + // state with valid changes + expect(screen.getByRole('button', { name: 'Save' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Reload' })).toBeEnabled(); + }); + }); + + test('handle the Reload button click', async () => { + renderComponent( + {}, + { isValid: true, nextGitConfig: { user: { name: 'new name', email: 'test@che' } } }, + ); + + // expect the name to be changed + expect(screen.getByTestId('user-name')).toHaveTextContent('new name'); + + screen.getByRole('button', { name: 'Reload' }).click(); + + expect(mockOnReload).toHaveBeenCalled(); + + // expect the name to be reverted + await waitFor(() => expect(screen.getByTestId('user-name')).toHaveTextContent('test')); + }); + + test('handle the Save button click', () => { + renderComponent( + {}, + { isValid: true, nextGitConfig: { user: { name: 'new name', email: 'test@che' } } }, + ); + + screen.getByRole('button', { name: 'Save' }).click(); + + expect(mockOnSave).toHaveBeenCalledWith({ user: { name: 'new name', email: 'test@che' } }); + }); +}); + +function getComponent(props: Partial = {}, state: Partial = {}): React.ReactElement { + const localState = { ...defaultState, ...state }; + return ( + + + + ); +} diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/index.tsx new file mode 100644 index 000000000..fccc82047 --- /dev/null +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/Form/index.tsx @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { Button, Flex, Form, PageSection, PageSectionVariants } from '@patternfly/react-core'; +import React, { FormEvent } from 'react'; + +import { GitConfigSectionUser } from '@/pages/UserPreferences/GitConfig/Form/SectionUser'; +import * as GitConfigStore from '@/store/GitConfig'; + +export type Props = { + isLoading: boolean; + gitConfig: GitConfigStore.GitConfig; + onSave: (gitConfig: GitConfigStore.GitConfig) => Promise; + onReload: () => Promise; +}; +export type State = { + isValid: boolean; + nextGitConfig: GitConfigStore.GitConfig | undefined; +}; + +export class GitConfigForm extends React.PureComponent { + constructor(props: Props) { + super(props); + + this.state = { + isValid: true, + nextGitConfig: undefined, + }; + } + + private handleSubmit(e: FormEvent): void { + e.preventDefault(); + e.stopPropagation(); + } + + private async handleSave(): Promise { + const nextGitConfig = { + ...this.props.gitConfig, + ...(this.state.nextGitConfig || {}), + }; + await this.props.onSave(nextGitConfig); + } + + private async handleReload(): Promise { + try { + await this.props.onReload(); + this.setState({ + nextGitConfig: undefined, + isValid: true, + }); + } catch (e) { + // ignore + } + } + + private handleChangeConfig(gitConfig: GitConfigStore.GitConfig, isValid: boolean): void { + this.setState({ + nextGitConfig: gitConfig, + isValid, + }); + } + + public render(): React.ReactElement { + const { gitConfig, isLoading } = this.props; + const { isValid, nextGitConfig } = this.state; + + const config = { ...gitConfig, ...(nextGitConfig || {}) }; + const isSaveDisabled = isLoading || isValid === false || nextGitConfig === undefined; + + return ( + +
this.handleSubmit(e)}> + this.handleChangeConfig(gitConfig, isValid)} + /> + + + + + +
+ ); + } +} diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/__tests__/__snapshots__/index.spec.tsx.snap deleted file mode 100644 index bc5f36aee..000000000 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/__tests__/__snapshots__/index.spec.tsx.snap +++ /dev/null @@ -1,50 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GitConfigSectionUser snapshot 1`] = ` -
-
-
-
-
-
- [user] -
- - -
-
-
-
-
-`; diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/index.tsx deleted file mode 100644 index 42c8c665e..000000000 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/SectionUser/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2018-2024 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ - -import { Form, FormSection, Panel, PanelMain, PanelMainBody } from '@patternfly/react-core'; -import * as React from 'react'; - -import { GitConfigUserEmail } from '@/pages/UserPreferences/GitConfig/SectionUser/Email'; -import { GitConfigUserName } from '@/pages/UserPreferences/GitConfig/SectionUser/Name'; -import { GitConfigUser } from '@/store/GitConfig'; - -export type Props = { - config: GitConfigUser; - onChange: (gitConfigUser: GitConfigUser) => void; -}; - -export class GitConfigSectionUser extends React.PureComponent { - private handleChange(partialConfig: Partial): void { - const { config, onChange } = this.props; - - onChange({ - ...config, - ...partialConfig, - }); - } - - public render(): React.ReactElement { - const { config } = this.props; - return ( - - - -
e.preventDefault()}> - - this.handleChange({ name })} - /> - this.handleChange({ email })} - /> - -
-
-
-
- ); - } -} diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/__tests__/__snapshots__/index.spec.tsx.snap index 9d8068ccb..9d640eb37 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/__tests__/__snapshots__/index.spec.tsx.snap +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/__tests__/__snapshots__/index.spec.tsx.snap @@ -10,21 +10,26 @@ exports[`GitConfig snapshot with gitconfig 1`] = `
-
-
+ + - user@che -
-
+
+ Save Config +
, @@ -38,44 +43,40 @@ exports[`GitConfig snapshot with no gitconfig 1`] = ` > , -
-
- -

- No gitconfig found -

-
+ } + viewBox="0 0 512 512" + width="1em" + > + + +

+ No gitconfig found +

-
, +
, ] `; diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/__tests__/index.spec.tsx index e6f48a56a..ee2cbb134 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/__tests__/index.spec.tsx @@ -27,14 +27,14 @@ import { ActionCreators } from '@/store/GitConfig'; import GitConfig from '..'; -jest.mock('../SectionUser'); +jest.mock('@/pages/UserPreferences/GitConfig/Form'); // mute output console.error = jest.fn(); const mockRequestGitConfig = jest.fn(); const mockUpdateGitConfig = jest.fn(); -jest.mock('../../../../store/GitConfig', () => ({ +jest.mock('@/store/GitConfig', () => ({ actionCreators: { requestGitConfig: (...args): AppThunk> => @@ -129,8 +129,8 @@ describe('GitConfig', () => { it('should update the git config and show a success notification', async () => { renderComponent(store); - const changeEmailButton = screen.getByRole('button', { name: 'Change Email' }); - userEvent.click(changeEmailButton); + const saveConfigButton = screen.getByRole('button', { name: 'Save Config' }); + userEvent.click(saveConfigButton); // mock should be called expect(mockUpdateGitConfig).toHaveBeenCalled(); @@ -150,8 +150,8 @@ describe('GitConfig', () => { mockUpdateGitConfig.mockRejectedValueOnce(new Error('update gitconfig error')); - const changeEmailButton = screen.getByRole('button', { name: 'Change Email' }); - userEvent.click(changeEmailButton); + const saveConfigButton = screen.getByRole('button', { name: 'Save Config' }); + userEvent.click(saveConfigButton); // mock should be called expect(mockUpdateGitConfig).toHaveBeenCalled(); @@ -183,6 +183,16 @@ describe('GitConfig', () => { } as AlertItem), ); }); + + it('should reload the gitconfig', async () => { + renderComponent(store); + + const reloadConfigButton = screen.getByRole('button', { name: 'Reload Config' }); + userEvent.click(reloadConfigButton); + + // mock should be called + expect(mockRequestGitConfig).toHaveBeenCalled(); + }); }); }); diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/index.tsx index a0fd88927..99a82e8fe 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/index.tsx +++ b/packages/dashboard-frontend/src/pages/UserPreferences/GitConfig/index.tsx @@ -18,14 +18,14 @@ import { connect, ConnectedProps } from 'react-redux'; import ProgressIndicator from '@/components/Progress'; import { lazyInject } from '@/inversify.config'; import { GitConfigEmptyState } from '@/pages/UserPreferences/GitConfig/EmptyState'; -import { GitConfigSectionUser } from '@/pages/UserPreferences/GitConfig/SectionUser'; +import { GitConfigForm } from '@/pages/UserPreferences/GitConfig/Form'; import { AppAlerts } from '@/services/alerts/appAlerts'; import { AppState } from '@/store'; import * as GitConfigStore from '@/store/GitConfig'; import { + selectGitConfig, selectGitConfigError, selectGitConfigIsLoading, - selectGitConfigUser, } from '@/store/GitConfig/selectors'; export type Props = MappedProps; @@ -35,33 +35,37 @@ class GitConfig extends React.PureComponent { private readonly appAlerts: AppAlerts; public async componentDidMount(): Promise { - const { gitConfigIsLoading, requestGitConfig } = this.props; + const { gitConfigIsLoading } = this.props; if (gitConfigIsLoading === true) { + this.init(); return; } - try { - await requestGitConfig(); - } catch (error) { - console.error(error); - } + await this.handleReload(); } public componentDidUpdate(prevProps: Props): void { + this.init(prevProps); + } + + private init(prevProps?: Props): void { const { gitConfigError } = this.props; - if (gitConfigError && gitConfigError !== prevProps.gitConfigError) { + const prevGitConfigError = prevProps?.gitConfigError; + if (gitConfigError && gitConfigError !== prevGitConfigError) { this.appAlerts.showAlert({ key: 'gitconfig-error', title: helpers.errors.getMessage(gitConfigError), variant: AlertVariant.danger, }); + } else if (gitConfigError === undefined && gitConfigError !== prevGitConfigError) { + this.appAlerts.removeAlert('gitconfig-error'); } } - private async handleChangeConfigUser(gitConfigUser: GitConfigStore.GitConfigUser): Promise { + private async handleSave(gitConfig: GitConfigStore.GitConfig): Promise { try { - await this.props.updateGitConfig(gitConfigUser); + await this.props.updateGitConfig(gitConfig); this.appAlerts.showAlert({ key: 'gitconfig-success', title: 'Gitconfig saved successfully.', @@ -72,31 +76,41 @@ class GitConfig extends React.PureComponent { } } + private async handleReload(): Promise { + try { + await this.props.requestGitConfig(); + } catch (error) { + console.error('Failed to reload gitconfig', error); + } + } + public render(): React.ReactElement { - const { gitConfigIsLoading, gitConfigUser } = this.props; + const { gitConfigIsLoading, gitConfig } = this.props; - const isEmpty = gitConfigUser === undefined; + const isEmpty = gitConfig === undefined; return ( - - {isEmpty ? ( - - ) : ( - this.handleChangeConfigUser(gitConfigUser)} + {isEmpty ? ( + + ) : ( + + await this.handleSave(gitConfig)} + onReload={async () => await this.handleReload()} /> - )} - + + )} ); } } const mapStateToProps = (state: AppState) => ({ - gitConfigUser: selectGitConfigUser(state), + gitConfig: selectGitConfig(state), gitConfigIsLoading: selectGitConfigIsLoading(state), gitConfigError: selectGitConfigError(state), }); diff --git a/packages/dashboard-frontend/src/store/GitConfig/__tests__/index.spec.ts b/packages/dashboard-frontend/src/store/GitConfig/__tests__/index.spec.ts index 6ab2ece5f..90c402990 100644 --- a/packages/dashboard-frontend/src/store/GitConfig/__tests__/index.spec.ts +++ b/packages/dashboard-frontend/src/store/GitConfig/__tests__/index.spec.ts @@ -134,8 +134,10 @@ describe('GitConfig store, actions', () => { it('should create REQUEST_GITCONFIG and RECEIVE_GITCONFIG when path the gitconfig', async () => { await store.dispatch( TestStore.actionCreators.updateGitConfig({ - name: 'testname', - email: 'test@email', + user: { + name: 'testname', + email: 'test@email', + }, }), ); @@ -166,7 +168,12 @@ describe('GitConfig store, actions', () => { try { await store.dispatch( - TestStore.actionCreators.updateGitConfig({ name: 'testname', email: 'testemail' }), + TestStore.actionCreators.updateGitConfig({ + user: { + name: 'testname', + email: 'testemail', + }, + }), ); } catch (e) { // ignore diff --git a/packages/dashboard-frontend/src/store/GitConfig/__tests__/selectors.spec.ts b/packages/dashboard-frontend/src/store/GitConfig/__tests__/selectors.spec.ts index b358c82cf..785f89a67 100644 --- a/packages/dashboard-frontend/src/store/GitConfig/__tests__/selectors.spec.ts +++ b/packages/dashboard-frontend/src/store/GitConfig/__tests__/selectors.spec.ts @@ -18,9 +18,9 @@ import { AppState } from '@/store'; import { FakeStoreBuilder } from '@/store/__mocks__/storeBuilder'; import * as TestStore from '@/store/GitConfig'; import { + selectGitConfig, selectGitConfigError, selectGitConfigIsLoading, - selectGitConfigUser, } from '@/store/GitConfig/selectors'; describe('GitConfig store, selectors', () => { @@ -59,10 +59,12 @@ describe('GitConfig store, selectors', () => { >; const state = fakeStore.getState(); - const gitconfig = selectGitConfigUser(state); + const gitconfig = selectGitConfig(state); expect(gitconfig).toEqual({ - name: 'user-che', - email: 'user@che', + user: { + name: 'user-che', + email: 'user@che', + }, }); }); diff --git a/packages/dashboard-frontend/src/store/GitConfig/index.ts b/packages/dashboard-frontend/src/store/GitConfig/index.ts index 4f8cbebea..f13fcc2bd 100644 --- a/packages/dashboard-frontend/src/store/GitConfig/index.ts +++ b/packages/dashboard-frontend/src/store/GitConfig/index.ts @@ -13,7 +13,7 @@ import common, { api, helpers } from '@eclipse-che/common'; import { fetchGitConfig, patchGitConfig } from '@/services/backend-client/gitConfigApi'; -import { GitConfigUser, KnownAction, Type } from '@/store/GitConfig/types'; +import { GitConfig, KnownAction, Type } from '@/store/GitConfig/types'; import { selectDefaultNamespace } from '@/store/InfrastructureNamespaces/selectors'; import { selectAsyncIsAuthorized, selectSanityCheckError } from '@/store/SanityCheck/selectors'; import { AUTHORIZED } from '@/store/sanityCheckMiddleware'; @@ -25,7 +25,7 @@ export * from './types'; export type ActionCreators = { requestGitConfig: () => AppThunk>; - updateGitConfig: (gitconfig: GitConfigUser) => AppThunk>; + updateGitConfig: (gitconfig: GitConfig) => AppThunk>; }; export const actionCreators: ActionCreators = { @@ -64,15 +64,13 @@ export const actionCreators: ActionCreators = { }, updateGitConfig: - (changedGitConfig: GitConfigUser): AppThunk> => + (changedGitConfig: GitConfig): AppThunk> => async (dispatch, getState): Promise => { const state = getState(); const namespace = selectDefaultNamespace(state).name; const { gitConfig } = state; const gitconfig = Object.assign(gitConfig.config || {}, { - gitconfig: { - user: changedGitConfig, - }, + gitconfig: changedGitConfig, } as api.IGitConfig); try { await dispatch({ type: Type.REQUEST_GITCONFIG, check: AUTHORIZED }); diff --git a/packages/dashboard-frontend/src/store/GitConfig/selectors.ts b/packages/dashboard-frontend/src/store/GitConfig/selectors.ts index 1cab1dd87..3af0bfc65 100644 --- a/packages/dashboard-frontend/src/store/GitConfig/selectors.ts +++ b/packages/dashboard-frontend/src/store/GitConfig/selectors.ts @@ -20,9 +20,9 @@ const selectState = (state: AppState) => state.gitConfig; export const selectGitConfigIsLoading = createSelector(selectState, state => state.isLoading); -export const selectGitConfigUser = createSelector( +export const selectGitConfig = createSelector( selectState, - (state: State) => state.config?.gitconfig.user, + (state: State) => state.config?.gitconfig, ); export const selectGitConfigError = createSelector(selectState, state => state.error); diff --git a/packages/dashboard-frontend/src/store/GitConfig/types.ts b/packages/dashboard-frontend/src/store/GitConfig/types.ts index 867a0d36c..157c6e990 100644 --- a/packages/dashboard-frontend/src/store/GitConfig/types.ts +++ b/packages/dashboard-frontend/src/store/GitConfig/types.ts @@ -15,7 +15,7 @@ import { Action } from 'redux'; import { SanityCheckAction } from '@/store/sanityCheckMiddleware'; -export type GitConfigUser = api.IGitConfig['gitconfig']['user']; +export type GitConfig = api.IGitConfig['gitconfig']; export interface State { isLoading: boolean;