From: guillaume-peoch-sonarsource Date: Fri, 17 Nov 2023 15:49:31 +0000 (+0100) Subject: SONAR-21054 Add Request Body details for PATCH and POST in WEB API v2 Doc X-Git-Tag: 10.4.0.87286~447 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=baf76207efc995683886b3aa50854942188a6809;p=sonarqube.git SONAR-21054 Add Request Body details for PATCH and POST in WEB API v2 Doc --- diff --git a/server/sonar-web/src/main/js/api/mocks/data/web-api.ts b/server/sonar-web/src/main/js/api/mocks/data/web-api.ts index 1e5b475ed78..76373b917fe 100644 --- a/server/sonar-web/src/main/js/api/mocks/data/web-api.ts +++ b/server/sonar-web/src/main/js/api/mocks/data/web-api.ts @@ -659,7 +659,7 @@ export const openApiTestData: OpenAPIV3.Document = { type: 'object', properties: { id: { type: 'integer', format: 'int64', example: 10 }, - name: { type: 'string', example: 'doggie' }, + name: { type: 'string', minLength: 3, maxLength: 100, example: 'doggie' }, category: { $ref: '#/components/schemas/Category' }, photoUrls: { type: 'array', @@ -675,6 +675,7 @@ export const openApiTestData: OpenAPIV3.Document = { type: 'string', description: 'pet status in the store', enum: ['available', 'pending', 'sold'], + deprecated: true, }, }, xml: { name: 'pet' }, diff --git a/server/sonar-web/src/main/js/apps/web-api-v2/__tests__/WebApiApp-it.tsx b/server/sonar-web/src/main/js/apps/web-api-v2/__tests__/WebApiApp-it.tsx index 2ab10d3d5bd..97bd6d7629b 100644 --- a/server/sonar-web/src/main/js/apps/web-api-v2/__tests__/WebApiApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/web-api-v2/__tests__/WebApiApp-it.tsx @@ -46,6 +46,9 @@ const ui = { requestHeader: byRole('list', { name: 'api_documentation.v2.request_subheader.header' }).byRole( 'listitem', ), + requestBodyParameter: byRole('list', { + name: 'api_documentation.v2.request_subheader.request_body', + }).byRole('listitem'), response: byRole('list', { name: 'api_documentation.v2.response_header' }).byRole('listitem'), }; @@ -96,6 +99,22 @@ it('should navigate between apis', async () => { expect(ui.pathParameter.query()).not.toBeInTheDocument(); expect(ui.requestHeader.query()).not.toBeInTheDocument(); expect(ui.requestBody.get()).toBeInTheDocument(); + expect(ui.requestBodyParameter.getAll()).toHaveLength(6); + expect(ui.requestBodyParameter.byRole('button').getAt(0)).toHaveAttribute( + 'aria-expanded', + 'false', + ); + await user.click(ui.requestBodyParameter.byRole('button').getAt(0)); + expect(ui.requestBodyParameter.byRole('button').getAt(0)).toHaveAttribute( + 'aria-expanded', + 'true', + ); + expect(ui.requestBodyParameter.getAt(0)).toHaveTextContent('name requiredmax: 100min: 3'); + await user.click(ui.requestBodyParameter.byRole('button').getAt(0)); + expect(ui.requestBodyParameter.byRole('button').getAt(0)).toHaveAttribute( + 'aria-expanded', + 'false', + ); expect(ui.response.byRole('button').getAt(0)).toHaveAttribute('aria-expanded', 'true'); expect(ui.response.byRole('button').getAt(2)).toHaveAttribute('aria-expanded', 'false'); expect(ui.response.getAt(0)).toHaveTextContent('200Successful operation'); @@ -138,6 +157,10 @@ it('should navigate between apis', async () => { expect(ui.queryParameter.getAt(1)).not.toHaveTextContent('deprecated'); await user.click(ui.queryParameter.byRole('button').getAt(1)); expect(ui.queryParameter.getAt(1)).toHaveTextContent('max: 5min: -1example: 3'); + + await user.click(ui.apiSidebarItem.getAt(7)); + expect(await screen.findByText('/api/v3/pet/{petId}/uploadImage')).toBeInTheDocument(); + expect(screen.getByText('no_data')).toBeInTheDocument(); }); it('should show About page', async () => { diff --git a/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiParameters.tsx b/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiParameters.tsx index d3f82f4a84c..61165198841 100644 --- a/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiParameters.tsx +++ b/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiParameters.tsx @@ -25,6 +25,7 @@ import React from 'react'; import { translate } from '../../../helpers/l10n'; import { ExcludeReferences } from '../types'; import { mapOpenAPISchema } from '../utils'; +import ApiRequestBodyParameters from './ApiRequestParameters'; import ApiResponseSchema from './ApiResponseSchema'; interface Props { @@ -53,15 +54,6 @@ export default function ApiParameters({ data }: Props) { return ( <> {translate('api_documentation.v2.parameter_header')} - {!requestBody && !data.parameters?.length && } - {requestBody && ( -
- - {translate('api_documentation.v2.request_subheader.request_body')} - - -
- )} {Object.entries(groupBy(data.parameters, (p) => p.in)).map(([group, parameters]) => (
@@ -103,7 +95,7 @@ export default function ApiParameters({ data }: Props) { text={`${translate('max')}: ${parameter.schema?.maximum}`} /> )} - {parameter.schema?.minimum && ( + {typeof parameter.schema?.minimum === 'number' && (
))} + {!requestBody && !data.parameters?.length && } + {requestBody && ( +
+ + {translate('api_documentation.v2.request_subheader.request_body')} + + + +
+ )} ); } diff --git a/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiRequestParameters.tsx b/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiRequestParameters.tsx new file mode 100644 index 00000000000..1888bd7847c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiRequestParameters.tsx @@ -0,0 +1,112 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { Accordion, Badge, TextMuted } from 'design-system'; +import { isEmpty } from 'lodash'; +import { OpenAPIV3 } from 'openapi-types'; +import React from 'react'; +import { translate } from '../../../helpers/l10n'; +import { ExcludeReferences } from '../types'; + +interface Props { + content?: Exclude['content'], undefined>; +} + +export default function ApiRequestBodyParameters({ content }: Readonly) { + const [openParameters, setOpenParameters] = React.useState([]); + + const toggleParameter = (parameter: string) => { + if (openParameters.includes(parameter)) { + setOpenParameters(openParameters.filter((n) => n !== parameter)); + } else { + setOpenParameters([...openParameters, parameter]); + } + }; + + const schema = + content && + (content['application/json']?.schema || content['application/merge-patch+json']?.schema); + + if (!schema?.properties || schema?.type !== 'object' || isEmpty(schema?.properties)) { + return null; + } + + const parameters = schema.properties; + const required = schema.required ?? []; + + const orderedKeys = Object.keys(parameters).sort((a, b) => { + if (required?.includes(a) && !required?.includes(b)) { + return -1; + } + if (!required?.includes(a) && required?.includes(b)) { + return 1; + } + return 0; + }); + + return ( +
    + {orderedKeys.map((key) => { + return ( + + {key}{' '} + {schema.required?.includes(key) && ( + {translate('required')} + )} + {parameters[key].deprecated && ( + + {translate('deprecated')} + + )} + + } + data={key} + onClick={() => toggleParameter(key)} + open={openParameters.includes(key)} + > +
    {parameters[key].description}
    + {parameters[key].maxLength && ( + + )} + {typeof parameters[key].minLength === 'number' && ( + + )} + {parameters[key].default !== undefined && ( + + )} +
    + ); + })} +
+ ); +}