diff options
author | Guillaume Peoc'h <guillaume.peoch@sonarsource.com> | 2022-10-27 16:57:58 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-11-01 20:03:09 +0000 |
commit | ef563f2a2e98629a8be54164e43095d672762c3c (patch) | |
tree | e23dc4147d924596b7354f0bc91ed044aa0f4a10 /server/sonar-web/src/main/js | |
parent | f437fd1533e60cdad6adab04514f44d13334209d (diff) | |
download | sonarqube-ef563f2a2e98629a8be54164e43095d672762c3c.tar.gz sonarqube-ef563f2a2e98629a8be54164e43095d672762c3c.zip |
SONAR-17527 Add main branch name during manual project creation
Diffstat (limited to 'server/sonar-web/src/main/js')
7 files changed, 199 insertions, 399 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index ea9c6f2621e..d05d132b1f7 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -86,6 +86,7 @@ export function deletePortfolio(portfolio: string): Promise<void | Response> { export function createProject(data: { name: string; project: string; + mainBranch: string; visibility?: Visibility; }): Promise<{ project: ProjectBase }> { return postJSON('/api/projects/create', data).catch(throwGlobalError); diff --git a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx index 696be7893d2..480c410f267 100644 --- a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx @@ -18,9 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import classNames from 'classnames'; -import { debounce } from 'lodash'; +import { debounce, isEmpty } from 'lodash'; import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; import { createProject, doesComponentExists } from '../../../api/components'; +import { getValue } from '../../../api/settings'; +import DocLink from '../../../components/common/DocLink'; import ProjectKeyInput from '../../../components/common/ProjectKeyInput'; import { SubmitButton } from '../../../components/controls/buttons'; import ValidationInput from '../../../components/controls/ValidationInput'; @@ -30,6 +33,7 @@ import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsEx import { translate } from '../../../helpers/l10n'; import { PROJECT_KEY_INVALID_CHARACTERS, validateProjectKey } from '../../../helpers/projects'; import { ProjectKeyValidationResult } from '../../../types/component'; +import { GlobalSettingKeys } from '../../../types/settings'; import { PROJECT_NAME_MAX_LEN } from './constants'; import CreateProjectPageHeader from './CreateProjectPageHeader'; import './ManualProjectCreate.css'; @@ -47,6 +51,9 @@ interface State { projectKeyError?: string; projectKeyTouched: boolean; validatingProjectKey: boolean; + mainBranchName: string; + mainBranchNameError?: string; + mainBranchNameTouched: boolean; submitting: boolean; } @@ -63,6 +70,8 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat submitting: false, projectKeyTouched: false, projectNameTouched: false, + mainBranchName: 'main', + mainBranchNameTouched: false, validatingProjectKey: false }; this.checkFreeKey = debounce(this.checkFreeKey, 250); @@ -70,12 +79,21 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat componentDidMount() { this.mounted = true; + this.fetchMainBranchName(); } componentWillUnmount() { this.mounted = false; } + fetchMainBranchName = async () => { + const mainBranchName = await getValue({ key: GlobalSettingKeys.MainBranchName }); + + if (this.mounted && mainBranchName.value !== undefined) { + this.setState({ mainBranchName: mainBranchName.value }); + } + }; + checkFreeKey = (key: string) => { this.setState({ validatingProjectKey: true }); @@ -98,23 +116,25 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat }; canSubmit(state: State): state is ValidState { - const { projectKey, projectKeyError, projectName, projectNameError } = state; + const { projectKey, projectKeyError, projectName, projectNameError, mainBranchName } = state; return Boolean( projectKeyError === undefined && projectNameError === undefined && - projectKey.length > 0 && - projectName.length > 0 + !isEmpty(projectKey) && + !isEmpty(projectName) && + !isEmpty(mainBranchName) ); } handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); - const { state } = this; - if (this.canSubmit(state)) { + const { projectKey, projectName, mainBranchName } = this.state; + if (this.canSubmit(this.state)) { this.setState({ submitting: true }); createProject({ - project: state.projectKey, - name: (state.projectName || state.projectKey).trim() + project: projectKey, + name: (projectName || projectKey).trim(), + mainBranch: mainBranchName }).then( ({ project }) => this.props.onProjectCreate(project.key), () => { @@ -158,6 +178,14 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat ); }; + handleBranchNameChange = (mainBranchName: string, fromUI = false) => { + this.setState({ + mainBranchName, + mainBranchNameError: this.validateMainBranchName(mainBranchName), + mainBranchNameTouched: fromUI + }); + }; + validateKey = (projectKey: string) => { const result = validateProjectKey(projectKey); return result === ProjectKeyValidationResult.Valid @@ -166,12 +194,19 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat }; validateName = (projectName: string) => { - if (projectName.length === 0) { + if (isEmpty(projectName)) { return translate('onboarding.create_project.display_name.error.empty'); } return undefined; }; + validateMainBranchName = (mainBranchName: string) => { + if (isEmpty(mainBranchName)) { + return translate('onboarding.create_project.main_branch_name.error.empty'); + } + return undefined; + }; + render() { const { projectKey, @@ -181,13 +216,18 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat projectNameError, projectNameTouched, validatingProjectKey, + mainBranchName, + mainBranchNameError, + mainBranchNameTouched, submitting } = this.state; const { branchesEnabled } = this.props; - const touched = !!(projectKeyTouched || projectNameTouched); + const touched = Boolean(projectKeyTouched || projectNameTouched); const projectNameIsInvalid = projectNameTouched && projectNameError !== undefined; const projectNameIsValid = projectNameTouched && projectNameError === undefined; + const mainBranchNameIsValid = mainBranchNameTouched && mainBranchNameError === undefined; + const mainBranchNameIsInvalid = mainBranchNameTouched && mainBranchNameError !== undefined; return ( <> @@ -230,6 +270,42 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat validating={validatingProjectKey} /> + <ValidationInput + className="form-field" + description={ + <FormattedMessage + id="onboarding.create_project.main_branch_name.description" + defaultMessage={translate( + 'onboarding.create_project.main_branch_name.description' + )} + values={{ + learn_more: ( + <DocLink to="/project-administration/project-existence"> + {translate('learn_more')} + </DocLink> + ) + }} + /> + } + error={mainBranchNameError} + id="main-branch-name" + isInvalid={mainBranchNameIsInvalid} + isValid={mainBranchNameIsValid} + label={translate('onboarding.create_project.main_branch_name')} + required={true}> + <input + id="main-branch-name" + className={classNames('input-super-large', { + 'is-invalid': mainBranchNameIsInvalid, + 'is-valid': mainBranchNameIsValid + })} + minLength={1} + onChange={e => this.handleBranchNameChange(e.currentTarget.value, true)} + type="text" + value={mainBranchName} + /> + </ValidationInput> + <SubmitButton disabled={!this.canSubmit(this.state) || submitting}> {translate('set_up')} </SubmitButton> diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx index 933d45fa607..de813558a76 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx @@ -31,6 +31,10 @@ jest.mock('../../../../api/components', () => ({ .mockImplementation(({ component }) => Promise.resolve(component === 'exists')) })); +jest.mock('../../../../api/settings', () => ({ + getValue: jest.fn().mockResolvedValue({ value: 'main' }) +})); + beforeEach(() => { jest.clearAllMocks(); }); @@ -70,12 +74,11 @@ it('should validate form input', async () => { ).toHaveValue('This-is-not-a-key-'); // Clear name - await user.click( - await screen.findByRole('textbox', { + await user.clear( + screen.getByRole('textbox', { name: 'onboarding.create_project.display_name field_required' }) ); - await user.keyboard('{Control>}a{/Control}{Backspace}'); expect( screen.getByRole('textbox', { name: 'onboarding.create_project.project_key field_required' }) ).toHaveValue(''); @@ -117,6 +120,16 @@ it('should validate form input', async () => { expect( await screen.findByText('onboarding.create_project.project_key.taken') ).toBeInTheDocument(); + + // Invalid main branch name + await user.clear( + screen.getByRole('textbox', { + name: 'onboarding.create_project.main_branch_name field_required' + }) + ); + expect( + await screen.findByText('onboarding.create_project.main_branch_name.error.empty') + ).toBeInTheDocument(); }); it('should submit form input', async () => { @@ -132,7 +145,11 @@ it('should submit form input', async () => { ); await user.keyboard('test'); await user.click(screen.getByRole('button', { name: 'set_up' })); - expect(createProject).toHaveBeenCalledWith({ name: 'test', project: 'test' }); + expect(createProject).toHaveBeenCalledWith({ + name: 'test', + project: 'test', + mainBranch: 'main' + }); expect(onProjectCreate).toHaveBeenCalled(); }); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx index c25cdf0455e..2471f9e93ea 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { createProject } from '../../api/components'; +import { getValue } from '../../api/settings'; import Link from '../../components/common/Link'; import VisibilitySelector from '../../components/common/VisibilitySelector'; import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons'; @@ -29,6 +30,7 @@ import MandatoryFieldMarker from '../../components/ui/MandatoryFieldMarker'; import MandatoryFieldsExplanation from '../../components/ui/MandatoryFieldsExplanation'; import { translate } from '../../helpers/l10n'; import { getProjectUrl } from '../../helpers/urls'; +import { GlobalSettingKeys } from '../../types/settings'; import { Visibility } from '../../types/types'; interface Props { @@ -45,6 +47,7 @@ interface State { visibility?: Visibility; // add index declaration to be able to do `this.setState({ [name]: value });` [x: string]: any; + mainBranchName: string; } export default class CreateProjectForm extends React.PureComponent<Props, State> { @@ -57,12 +60,14 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> key: '', loading: false, name: '', - visibility: props.defaultProjectVisibility + visibility: props.defaultProjectVisibility, + mainBranchName: 'main' }; } componentDidMount() { this.mounted = true; + this.fetchMainBranchName(); } componentDidUpdate() { @@ -78,6 +83,14 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> this.mounted = false; } + fetchMainBranchName = async () => { + const mainBranchName = await getValue({ key: GlobalSettingKeys.MainBranchName }); + + if (this.mounted && mainBranchName.value !== undefined) { + this.setState({ mainBranchName: mainBranchName.value }); + } + }; + handleInputChange = (event: React.SyntheticEvent<HTMLInputElement>) => { const { name, value } = event.currentTarget; this.setState({ [name]: value }); @@ -89,11 +102,13 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); + const { name, key, mainBranchName, visibility } = this.state; const data = { - name: this.state.name, - project: this.state.key, - visibility: this.state.visibility + name, + project: key, + mainBranch: mainBranchName, + visibility }; this.setState({ loading: true }); @@ -159,7 +174,7 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> <MandatoryFieldsExplanation className="modal-field" /> <div className="modal-field"> <label htmlFor="create-project-name"> - {translate('name')} + {translate('onboarding.create_project.display_name')} <MandatoryFieldMarker /> </label> <input @@ -175,7 +190,7 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> </div> <div className="modal-field"> <label htmlFor="create-project-key"> - {translate('key')} + {translate('onboarding.create_project.project_key')} <MandatoryFieldMarker /> </label> <input @@ -189,6 +204,21 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> /> </div> <div className="modal-field"> + <label htmlFor="create-project-main-branch-name"> + {translate('onboarding.create_project.main_branch_name')} + <MandatoryFieldMarker /> + </label> + <input + id="create-project-main-branch-name" + maxLength={400} + name="mainBranchName" + onChange={this.handleInputChange} + required={true} + type="text" + value={this.state.mainBranchName} + /> + </div> + <div className="modal-field"> <label>{translate('visibility')}</label> <VisibilitySelector canTurnToPrivate={defaultProjectVisibility !== undefined} diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx index 34923e19f20..328a10ee46d 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx @@ -17,49 +17,67 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/* eslint-disable import/first */ -jest.mock('../../../api/components', () => ({ - createProject: jest.fn(({ name }: { name: string }) => - Promise.resolve({ project: { key: name, name } }) - ) -})); -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import * as React from 'react'; -import { change, submit, waitAndUpdate } from '../../../helpers/testUtils'; +import { createProject } from '../../../api/components'; import CreateProjectForm from '../CreateProjectForm'; -const createProject = require('../../../api/components').createProject as jest.Mock<any>; +jest.mock('../../../api/components', () => ({ + createProject: jest.fn().mockResolvedValue({}), + doesComponentExists: jest + .fn() + .mockImplementation(({ component }) => Promise.resolve(component === 'exists')) +})); + +jest.mock('../../../api/settings', () => ({ + getValue: jest.fn().mockResolvedValue({ value: 'main' }) +})); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +it('should render all inputs and create a project', async () => { + const user = userEvent.setup(); + renderCreateProjectForm(); -it('creates project', async () => { - const wrapper = shallow( - <CreateProjectForm - defaultProjectVisibility="public" - onClose={jest.fn()} - onProjectCreated={jest.fn()} - /> + await user.type( + screen.getByRole('textbox', { + name: 'onboarding.create_project.display_name field_required' + }), + 'ProjectName' ); - (wrapper.instance() as CreateProjectForm).mounted = true; - expect(wrapper).toMatchSnapshot(); - change(wrapper.find('input[name="name"]'), 'name', { - currentTarget: { name: 'name', value: 'name' } - }); - change(wrapper.find('input[name="key"]'), 'key', { - currentTarget: { name: 'key', value: 'key' } - }); - wrapper.find('VisibilitySelector').prop<Function>('onChange')('private'); - wrapper.update(); - expect(wrapper).toMatchSnapshot(); + await user.type( + screen.getByRole('textbox', { + name: 'onboarding.create_project.project_key field_required' + }), + 'ProjectKey' + ); + + expect( + screen.getByRole('textbox', { + name: 'onboarding.create_project.main_branch_name field_required' + }) + ).toHaveValue('main'); - submit(wrapper.find('form')); + await user.type( + screen.getByRole('textbox', { + name: 'onboarding.create_project.main_branch_name field_required' + }), + '{Control>}a{/Control}{Backspace}ProjectMainBranch' + ); + + await user.click(screen.getByRole('button', { name: 'create' })); expect(createProject).toHaveBeenCalledWith({ - name: 'name', - project: 'key', - visibility: 'private' + name: 'ProjectName', + project: 'ProjectKey', + mainBranch: 'ProjectMainBranch' }); - expect(wrapper).toMatchSnapshot(); - - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); }); + +function renderCreateProjectForm(props: Partial<CreateProjectForm['props']> = {}) { + render(<CreateProjectForm onClose={jest.fn()} onProjectCreated={jest.fn()} {...props} />); +} diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap deleted file mode 100644 index 1a4fc2524bd..00000000000 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap +++ /dev/null @@ -1,343 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`creates project 1`] = ` -<Modal - contentLabel="modal form" - onRequestClose={[MockFunction]} -> - <form - id="create-project-form" - onSubmit={[Function]} - > - <header - className="modal-head" - > - <h2> - qualifiers.create.TRK - </h2> - </header> - <div - className="modal-body" - > - <MandatoryFieldsExplanation - className="modal-field" - /> - <div - className="modal-field" - > - <label - htmlFor="create-project-name" - > - name - <MandatoryFieldMarker /> - </label> - <input - autoFocus={true} - id="create-project-name" - maxLength={2000} - name="name" - onChange={[Function]} - required={true} - type="text" - value="" - /> - </div> - <div - className="modal-field" - > - <label - htmlFor="create-project-key" - > - key - <MandatoryFieldMarker /> - </label> - <input - id="create-project-key" - maxLength={400} - name="key" - onChange={[Function]} - required={true} - type="text" - value="" - /> - </div> - <div - className="modal-field" - > - <label> - visibility - </label> - <VisibilitySelector - canTurnToPrivate={true} - className="little-spacer-top" - onChange={[Function]} - visibility="public" - /> - </div> - </div> - <footer - className="modal-foot" - > - <SubmitButton - disabled={false} - id="create-project-submit" - > - create - </SubmitButton> - <ResetButtonLink - id="create-project-cancel" - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </footer> - </form> -</Modal> -`; - -exports[`creates project 2`] = ` -<Modal - contentLabel="modal form" - onRequestClose={[MockFunction]} -> - <form - id="create-project-form" - onSubmit={[Function]} - > - <header - className="modal-head" - > - <h2> - qualifiers.create.TRK - </h2> - </header> - <div - className="modal-body" - > - <MandatoryFieldsExplanation - className="modal-field" - /> - <div - className="modal-field" - > - <label - htmlFor="create-project-name" - > - name - <MandatoryFieldMarker /> - </label> - <input - autoFocus={true} - id="create-project-name" - maxLength={2000} - name="name" - onChange={[Function]} - required={true} - type="text" - value="name" - /> - </div> - <div - className="modal-field" - > - <label - htmlFor="create-project-key" - > - key - <MandatoryFieldMarker /> - </label> - <input - id="create-project-key" - maxLength={400} - name="key" - onChange={[Function]} - required={true} - type="text" - value="key" - /> - </div> - <div - className="modal-field" - > - <label> - visibility - </label> - <VisibilitySelector - canTurnToPrivate={true} - className="little-spacer-top" - onChange={[Function]} - visibility="private" - /> - </div> - </div> - <footer - className="modal-foot" - > - <SubmitButton - disabled={false} - id="create-project-submit" - > - create - </SubmitButton> - <ResetButtonLink - id="create-project-cancel" - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </footer> - </form> -</Modal> -`; - -exports[`creates project 3`] = ` -<Modal - contentLabel="modal form" - onRequestClose={[MockFunction]} -> - <form - id="create-project-form" - onSubmit={[Function]} - > - <header - className="modal-head" - > - <h2> - qualifiers.create.TRK - </h2> - </header> - <div - className="modal-body" - > - <MandatoryFieldsExplanation - className="modal-field" - /> - <div - className="modal-field" - > - <label - htmlFor="create-project-name" - > - name - <MandatoryFieldMarker /> - </label> - <input - autoFocus={true} - id="create-project-name" - maxLength={2000} - name="name" - onChange={[Function]} - required={true} - type="text" - value="name" - /> - </div> - <div - className="modal-field" - > - <label - htmlFor="create-project-key" - > - key - <MandatoryFieldMarker /> - </label> - <input - id="create-project-key" - maxLength={400} - name="key" - onChange={[Function]} - required={true} - type="text" - value="key" - /> - </div> - <div - className="modal-field" - > - <label> - visibility - </label> - <VisibilitySelector - canTurnToPrivate={true} - className="little-spacer-top" - onChange={[Function]} - visibility="private" - /> - </div> - </div> - <footer - className="modal-foot" - > - <i - className="spinner spacer-right" - /> - <SubmitButton - disabled={true} - id="create-project-submit" - > - create - </SubmitButton> - <ResetButtonLink - id="create-project-cancel" - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </footer> - </form> -</Modal> -`; - -exports[`creates project 4`] = ` -<Modal - contentLabel="modal form" - onRequestClose={[MockFunction]} -> - <div> - <header - className="modal-head" - > - <h2> - qualifiers.create.TRK - </h2> - </header> - <div - className="modal-body" - > - <Alert - variant="success" - > - <FormattedMessage - defaultMessage="projects_management.project_has_been_successfully_created" - id="projects_management.project_has_been_successfully_created" - values={ - Object { - "project": <ForwardRef(Link) - to={ - Object { - "pathname": "/dashboard", - "search": "?id=name", - } - } - > - name - </ForwardRef(Link)>, - } - } - /> - </Alert> - </div> - <footer - className="modal-foot" - > - <ResetButtonLink - id="create-project-close" - innerRef={[Function]} - onClick={[MockFunction]} - > - close - </ResetButtonLink> - </footer> - </div> -</Modal> -`; diff --git a/server/sonar-web/src/main/js/types/settings.ts b/server/sonar-web/src/main/js/types/settings.ts index 32cfbc2004a..d1de09e3f7f 100644 --- a/server/sonar-web/src/main/js/types/settings.ts +++ b/server/sonar-web/src/main/js/types/settings.ts @@ -38,7 +38,8 @@ export enum GlobalSettingKeys { DeveloperAggregatedInfoDisabled = 'sonar.developerAggregatedInfo.disabled', UpdatecenterActivated = 'sonar.updatecenter.activate', DisplayAnnouncementMessage = 'sonar.announcement.displayMessage', - AnnouncementMessage = 'sonar.announcement.message' + AnnouncementMessage = 'sonar.announcement.message', + MainBranchName = 'sonar.projectCreation.mainBranchName' } export type SettingDefinitionAndValue = { |