diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2020-04-06 14:27:19 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2020-04-15 20:03:38 +0000 |
commit | 661873873ffca37fe149bfbf1775cdda8955ea8b (patch) | |
tree | a3a64918feafee6a71a86bceb1d190f54d294181 /server | |
parent | 613156f73891c1348fe01065ea0b6694e82d3f41 (diff) | |
download | sonarqube-661873873ffca37fe149bfbf1775cdda8955ea8b.tar.gz sonarqube-661873873ffca37fe149bfbf1775cdda8955ea8b.zip |
SONAR-13190 Add tags to Applications information sidedrawer
Diffstat (limited to 'server')
7 files changed, 292 insertions, 48 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index df296aa6181..2b7ddb74492 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -86,6 +86,10 @@ export function searchProjectTags(data?: { ps?: number; q?: string }): Promise<a return getJSON('/api/project_tags/search', data).catch(throwGlobalError); } +export function setApplicationTags(data: { application: string; tags: string }): Promise<void> { + return post('/api/applications/set_tags', data); +} + export function setProjectTags(data: { project: string; tags: string }): Promise<void> { return post('/api/project_tags/set', data); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformationRenderer.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformationRenderer.tsx index 1aff0f9cf33..25bab1a9d05 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformationRenderer.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformationRenderer.tsx @@ -53,27 +53,23 @@ export function ProjectInformationRenderer(props: ProjectInformationRendererProp </div> <div className="overflow-y-auto"> - {(component.description || !isApp) && ( - <div className="big-padded bordered-bottom"> - <div className="display-flex-center"> - <h3 className="spacer-right">{translate('project.info.description')}</h3> - {component.visibility && ( - <PrivacyBadgeContainer - organization={undefined} - qualifier={component.qualifier} - tooltipProps={{ projectKey: component.key }} - visibility={component.visibility} - /> - )} - </div> - - {component.description && <p className="spacer-bottom">{component.description}</p>} - - {!isApp && ( - <MetaTags component={component} onComponentChange={props.onComponentChange} /> + <div className="big-padded bordered-bottom"> + <div className="display-flex-center"> + <h3 className="spacer-right">{translate('project.info.description')}</h3> + {component.visibility && ( + <PrivacyBadgeContainer + organization={undefined} + qualifier={component.qualifier} + tooltipProps={{ projectKey: component.key }} + visibility={component.visibility} + /> )} </div> - )} + + {component.description && <p>{component.description}</p>} + + <MetaTags component={component} onComponentChange={props.onComponentChange} /> + </div> <div className="big-padded bordered-bottom it__project-loc-value"> <MetaSize component={component} measures={measures} /> diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/ProjectInformationRenderer-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/ProjectInformationRenderer-test.tsx index 577b2e387d2..26f2ab9ff25 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/ProjectInformationRenderer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/ProjectInformationRenderer-test.tsx @@ -43,6 +43,11 @@ it('should render an app correctly', () => { expect(shallowRender({ component })).toMatchSnapshot('default'); }); +it('should render without description', () => { + const component = mockComponent({ description: undefined }); + expect(shallowRender({ component })).toMatchSnapshot(); +}); + it('should handle missing quality profiles and quality gates', () => { expect( shallowRender({ diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformationRenderer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformationRenderer-test.tsx.snap index d071690097a..8819cf6af17 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformationRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformationRenderer-test.tsx.snap @@ -274,6 +274,45 @@ exports[`should render an app correctly: default 1`] = ` className="overflow-y-auto" > <div + className="big-padded bordered-bottom" + > + <div + className="display-flex-center" + > + <h3 + className="spacer-right" + > + project.info.description + </h3> + </div> + <MetaTags + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "APP", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + } + } + onComponentChange={[MockFunction]} + /> + </div> + <div className="big-padded bordered-bottom it__project-loc-value" > <MetaSize @@ -968,3 +1007,160 @@ exports[`should render correctly: with notifications 1`] = ` </div> </Fragment> `; + +exports[`should render without description 1`] = ` +<Fragment> + <div> + <h2 + className="big-padded bordered-bottom" + > + project.info.title + </h2> + </div> + <div + className="overflow-y-auto" + > + <div + className="big-padded bordered-bottom" + > + <div + className="display-flex-center" + > + <h3 + className="spacer-right" + > + project.info.description + </h3> + </div> + <MetaTags + component={ + Object { + "breadcrumbs": Array [], + "description": undefined, + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + } + } + onComponentChange={[MockFunction]} + /> + </div> + <div + className="big-padded bordered-bottom it__project-loc-value" + > + <MetaSize + component={ + Object { + "breadcrumbs": Array [], + "description": undefined, + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + } + } + measures={Array []} + /> + </div> + <div + className="big-padded bordered-bottom" + > + <MetaQualityGate + qualityGate={ + Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + } + } + /> + <Connect(MetaQualityProfiles) + headerClassName="big-spacer-top" + profiles={ + Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ] + } + /> + </div> + <MetaLinks + component={ + Object { + "breadcrumbs": Array [], + "description": undefined, + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + } + } + /> + <div + className="big-padded bordered-bottom" + > + <MetaKey + componentKey="my-project" + qualifier="TRK" + /> + </div> + <Memo(DrawerLink) + label="overview.badges.get_badge.TRK" + onPageChange={[MockFunction]} + to={1} + /> + <Memo(DrawerLink) + label="project.info.to_notifications" + onPageChange={[MockFunction]} + to={2} + /> + </div> +</Fragment> +`; diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaTags.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaTags.tsx index 1e7b3496586..03650b13f67 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaTags.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaTags.tsx @@ -22,8 +22,9 @@ import { ButtonLink } from 'sonar-ui-common/components/controls/buttons'; import Dropdown from 'sonar-ui-common/components/controls/Dropdown'; import { PopupPlacement } from 'sonar-ui-common/components/ui/popups'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import { setProjectTags } from '../../../../../../api/components'; +import { setApplicationTags, setProjectTags } from '../../../../../../api/components'; import TagsList from '../../../../../../components/tags/TagsList'; +import { ComponentQualifier } from '../../../../../../types/component'; import MetaTagsSelector from './MetaTagsSelector'; interface Props { @@ -46,11 +47,24 @@ export default class MetaTags extends React.PureComponent<Props> { right: containerPos.width - eltPos.width }); + setTags = (values: string[]) => { + const { component } = this.props; + + if (component.qualifier === ComponentQualifier.Application) { + return setApplicationTags({ + application: component.key, + tags: values.join(',') + }); + } else { + return setProjectTags({ + project: component.key, + tags: values.join(',') + }); + } + }; + handleSetProjectTags = (values: string[]) => { - setProjectTags({ - project: this.props.component.key, - tags: values.join(',') - }).then( + this.setTags(values).then( () => this.props.onComponentChange({ tags: values }), () => {} ); @@ -62,7 +76,7 @@ export default class MetaTags extends React.PureComponent<Props> { if (this.canUpdateTags()) { return ( - <div className="project-info-tags" ref={card => (this.card = card)}> + <div className="big-spacer-top project-info-tags" ref={card => (this.card = card)}> <Dropdown closeOnClick={false} closeOnClickOutside={true} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaTags-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaTags-test.tsx index f8464ab66c5..d6df5ed4b7c 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaTags-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaTags-test.tsx @@ -19,36 +19,65 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { setApplicationTags, setProjectTags } from '../../../../../../../api/components'; import { mockComponent } from '../../../../../../../helpers/testMocks'; +import { ComponentQualifier } from '../../../../../../../types/component'; import MetaTags from '../MetaTags'; -const component = mockComponent({ - configuration: { - showSettings: false - } -}); +jest.mock('../../../../../../../api/components', () => ({ + setApplicationTags: jest.fn().mockResolvedValue(true), + setProjectTags: jest.fn().mockResolvedValue(true) +})); -const componentWithTags = mockComponent({ - key: 'my-second-project', - tags: ['foo', 'bar'], - configuration: { - showSettings: true - }, - name: 'MySecondProject' +beforeEach(() => { + jest.clearAllMocks(); }); it('should render without tags and admin rights', () => { - expect( - shallow(<MetaTags component={component} onComponentChange={jest.fn()} />, { - disableLifecycleMethods: true - }) - ).toMatchSnapshot(); + expect(shallowRender()).toMatchSnapshot(); }); it('should render with tags and admin rights', () => { - expect( - shallow(<MetaTags component={componentWithTags} onComponentChange={jest.fn()} />, { - disableLifecycleMethods: true - }) - ).toMatchSnapshot(); + const component = mockComponent({ + key: 'my-second-project', + tags: ['foo', 'bar'], + configuration: { + showSettings: true + }, + name: 'MySecondProject' + }); + + expect(shallowRender({ component })).toMatchSnapshot(); +}); + +it('should set tags for a project', () => { + const wrapper = shallowRender(); + + wrapper.instance().handleSetProjectTags(['tag1', 'tag2']); + + expect(setProjectTags).toHaveBeenCalled(); + expect(setApplicationTags).not.toHaveBeenCalled(); }); + +it('should set tags for an app', () => { + const wrapper = shallowRender({ + component: mockComponent({ qualifier: ComponentQualifier.Application }) + }); + + wrapper.instance().handleSetProjectTags(['tag1', 'tag2']); + + expect(setProjectTags).not.toHaveBeenCalled(); + expect(setApplicationTags).toHaveBeenCalled(); +}); + +function shallowRender(overrides: Partial<MetaTags['props']> = {}) { + const component = mockComponent({ + configuration: { + showSettings: false + } + }); + + return shallow<MetaTags>( + <MetaTags component={component} onComponentChange={jest.fn()} {...overrides} /> + ); +} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap index c415bdfdab7..5e9a5365588 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap @@ -2,7 +2,7 @@ exports[`should render with tags and admin rights 1`] = ` <div - className="project-info-tags" + className="big-spacer-top project-info-tags" > <Dropdown closeOnClick={false} |