diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2023-12-07 15:22:38 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-12-07 20:02:52 +0000 |
commit | 63d95c140def63a91bb55caeb50c5f9b9370793f (patch) | |
tree | 329a7cbd756df346c52c712c054f2a93e8b6d515 /server | |
parent | 664183e0b9b31a5429bbec63f35812f968a62426 (diff) | |
download | sonarqube-63d95c140def63a91bb55caeb50c5f9b9370793f.tar.gz sonarqube-63d95c140def63a91bb55caeb50c5f9b9370793f.zip |
SONAR-21089 Improve WebApi2 menu
Diffstat (limited to 'server')
-rw-r--r-- | server/sonar-web/src/main/js/apps/web-api-v2/__tests__/WebApiApp-it.tsx | 5 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/web-api-v2/components/ApiSidebar.tsx | 93 |
2 files changed, 68 insertions, 30 deletions
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 10599c6f00b..5f6fd8ed667 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 @@ -162,8 +162,11 @@ it('should navigate between apis', async () => { await user.click(ui.queryParameter.byRole('button').getAt(0)); expect(ui.queryParameter.getAt(0)).not.toHaveTextContent('default: available'); - await user.click(ui.apiSidebarItem.getAt(5)); + await user.click(ui.apiSidebarItem.getAt(4)); expect(await screen.findByText('/api/v3/pet/{petId}')).toBeInTheDocument(); + expect( + await screen.findByText('Updates a pet in the store with form data', { selector: 'h1' }), + ).toBeInTheDocument(); expect(ui.queryParameter.getAll()).toHaveLength(2); expect(ui.pathParameter.getAll()).toHaveLength(1); expect(ui.requestHeader.query()).not.toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiSidebar.tsx b/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiSidebar.tsx index 6e0c142ac5f..6df26597fed 100644 --- a/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiSidebar.tsx +++ b/server/sonar-web/src/main/js/apps/web-api-v2/components/ApiSidebar.tsx @@ -20,18 +20,22 @@ import classNames from 'classnames'; import { Badge, + BasicSeparator, Checkbox, HelperHintIcon, InputSearch, Link, SubnavigationAccordion, SubnavigationItem, + SubnavigationSubheading, } from 'design-system'; +import { sortBy } from 'lodash'; import { OpenAPIV3 } from 'openapi-types'; -import React, { useMemo, useState } from 'react'; +import React, { Fragment, useMemo, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import HelpTooltip from '../../../components/controls/HelpTooltip'; import { translate } from '../../../helpers/l10n'; +import { Dict } from '../../../types/types'; import { URL_DIVIDER, getApiEndpointKey, getMethodClassName } from '../utils'; interface Api { @@ -44,6 +48,13 @@ interface Props { apisList: Api[]; } +const METHOD_ORDER: Dict<number> = { + post: 0, + get: 1, + patch: 2, + delete: 3, +}; + export default function ApiSidebar({ apisList, docInfo }: Readonly<Props>) { const [search, setSearch] = useState(''); const [showInternal, setShowInternal] = useState(false); @@ -112,38 +123,62 @@ export default function ApiSidebar({ apisList, docInfo }: Readonly<Props>) { key={group} id={`web-api-${group}`} > - {apis.map(({ method, name, info }) => ( - <SubnavigationItem - active={name === activeApi[0] && method === activeApi[1]} - key={getApiEndpointKey(name, method)} - onClick={handleApiClick} - value={getApiEndpointKey(name, method)} - > - <div className="sw-flex sw-gap-2"> - <Badge className={classNames('sw-self-center', getMethodClassName(method))}> - {method.toUpperCase()} - </Badge> - <div>{info.summary ?? name}</div> + {sortBy(apis, (a) => [a.name, METHOD_ORDER[a.method]]).map( + ({ method, name, info }, index, sorted) => { + const resourceName = getResourceFromName(name); + const previousResourceName = + index > 0 ? getResourceFromName(sorted[index - 1].name) : undefined; + const isNewResource = resourceName !== previousResourceName; - {(info['x-internal'] || info.deprecated) && ( - <div className="sw-flex sw-flex-col sw-justify-center sw-gap-2"> - {info['x-internal'] && ( - <Badge variant="new" className="sw-self-center"> - {translate('internal')} - </Badge> - )} - {info.deprecated && ( - <Badge variant="deleted" className="sw-self-center"> - {translate('deprecated')} + return ( + <Fragment key={getApiEndpointKey(name, method)}> + {index > 0 && isNewResource && <BasicSeparator />} + {(index === 0 || isNewResource) && ( + <SubnavigationSubheading>{resourceName}</SubnavigationSubheading> + )} + <SubnavigationItem + active={name === activeApi[0] && method === activeApi[1]} + onClick={handleApiClick} + value={getApiEndpointKey(name, method)} + > + <div className="sw-flex sw-gap-2"> + <Badge className={classNames('sw-self-center', getMethodClassName(method))}> + {method.toUpperCase()} </Badge> - )} - </div> - )} - </div> - </SubnavigationItem> - ))} + <div>{info.summary ?? name}</div> + + {(info['x-internal'] || info.deprecated) && ( + <div className="sw-flex sw-flex-col sw-justify-center sw-gap-2"> + {info['x-internal'] && ( + <Badge variant="new" className="sw-self-center"> + {translate('internal')} + </Badge> + )} + {info.deprecated && ( + <Badge variant="deleted" className="sw-self-center"> + {translate('deprecated')} + </Badge> + )} + </div> + )} + </div> + </SubnavigationItem> + </Fragment> + ); + }, + )} </SubnavigationAccordion> ))} </> ); } + +function getResourceFromName(name: string) { + const parts = name.split('/').slice(2); // remove domain + pre-slash empty string + + if (name.endsWith('}')) { + parts.pop(); // remove the resource id + } + + return parts.join('/'); +} |