diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2022-01-25 17:40:27 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-01-27 20:03:04 +0000 |
commit | 0a563e3e59e9802090424591c66f623baeeb3a2f (patch) | |
tree | c69b4715080831bc464fd6a410b2bb11c7906c92 | |
parent | 1a2f7a181ffb7fb62b9d6e7c672462a84e2fda84 (diff) | |
download | sonarqube-0a563e3e59e9802090424591c66f623baeeb3a2f.tar.gz sonarqube-0a563e3e59e9802090424591c66f623baeeb3a2f.zip |
SONAR-15464 project settings search
8 files changed, 98 insertions, 25 deletions
diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts index 5e9861a8cdd..cea516047c7 100644 --- a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { mockComponent } from '../../../helpers/mocks/component'; import { mockDefinition } from '../../../helpers/mocks/settings'; import { Setting, @@ -90,10 +91,12 @@ describe('buildSettingLink', () => { it.each([ [ mockDefinition({ key: 'anykey' }), + undefined, { hash: '#anykey', pathname: '/admin/settings', query: { category: 'foo category' } } ], [ mockDefinition({ key: 'sonar.auth.gitlab.name' }), + undefined, { hash: '#sonar.auth.gitlab.name', pathname: '/admin/settings', @@ -102,6 +105,7 @@ describe('buildSettingLink', () => { ], [ mockDefinition({ key: 'sonar.auth.github.token' }), + undefined, { hash: '#sonar.auth.github.token', pathname: '/admin/settings', @@ -110,13 +114,23 @@ describe('buildSettingLink', () => { ], [ mockDefinition({ key: 'sonar.almintegration.azure' }), + undefined, { hash: '#sonar.almintegration.azure', pathname: '/admin/settings', query: { alm: 'azure', category: 'foo category' } } + ], + [ + mockDefinition({ key: 'defKey' }), + mockComponent({ key: 'componentKey' }), + { + hash: '#defKey', + pathname: '/project/settings', + query: { id: 'componentKey', category: 'foo category' } + } ] - ])('should work as expected', (definition, expectedUrl) => { - expect(buildSettingLink(definition)).toEqual(expectedUrl); + ])('should work as expected', (definition, component, expectedUrl) => { + expect(buildSettingLink(definition, component)).toEqual(expectedUrl); }); }); diff --git a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx index b6052ab4c85..b18ad5646fb 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx @@ -17,7 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; import * as React from 'react'; import InstanceMessage from '../../../components/common/InstanceMessage'; import { translate } from '../../../helpers/l10n'; @@ -37,15 +36,12 @@ export default function PageHeader({ component }: PageHeaderProps) { ); return ( - <header className={classNames('top-bar-outer', { 'with-search': component === undefined })}> + <header className="top-bar-outer"> <div className="top-bar"> - <div - className={classNames('top-bar-inner bordered-bottom big-padded-top padded-bottom', { - 'with-search': component === undefined - })}> + <div className="top-bar-inner bordered-bottom big-padded-top padded-bottom"> <h1 className="page-title">{title}</h1> <div className="page-description spacer-top">{description}</div> - {!component && <SettingsSearch className="big-spacer-top" />} + <SettingsSearch className="big-spacer-top" component={component} /> </div> </div> </header> diff --git a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx index 0d3bed68fd4..d868ed7ad30 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx @@ -26,11 +26,16 @@ import { withRouter } from '../../../components/hoc/withRouter'; import { KeyboardCodes } from '../../../helpers/keycodes'; import { getSettingsAppAllDefinitions, Store } from '../../../store/rootReducer'; import { SettingCategoryDefinition } from '../../../types/settings'; -import { ADDITIONAL_SETTING_DEFINITIONS, buildSettingLink } from '../utils'; +import { + ADDITIONAL_PROJECT_SETTING_DEFINITIONS, + ADDITIONAL_SETTING_DEFINITIONS, + buildSettingLink +} from '../utils'; import SettingsSearchRenderer from './SettingsSearchRenderer'; interface Props { className?: string; + component?: T.Component; definitions: SettingCategoryDefinition[]; router: InjectedRouter; } @@ -58,7 +63,9 @@ export class SettingsSearch extends React.Component<Props, State> { this.doSearch = debounce(this.doSearch, DEBOUNCE_DELAY); this.handleFocus = debounce(this.handleFocus, DEBOUNCE_DELAY); - const definitions = props.definitions.concat(ADDITIONAL_SETTING_DEFINITIONS); + const definitions = props.definitions.concat( + props.component ? ADDITIONAL_PROJECT_SETTING_DEFINITIONS : ADDITIONAL_SETTING_DEFINITIONS + ); this.index = this.buildSearchIndex(definitions); this.definitionsByKey = keyBy(definitions, 'key'); } @@ -171,11 +178,12 @@ export class SettingsSearch extends React.Component<Props, State> { }; render() { - const { className } = this.props; + const { className, component } = this.props; return ( <SettingsSearchRenderer className={className} + component={component} onClickOutside={this.hideResults} onMouseOverResult={this.handleMouseOverResult} onSearchInputChange={this.handleSearchChange} diff --git a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx index 38d72710fbc..f9d100058d3 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx @@ -30,6 +30,7 @@ import { buildSettingLink, isRealSettingKey } from '../utils'; export interface SettingsSearchRendererProps { className?: string; + component?: T.Component; results?: SettingCategoryDefinition[]; searchQuery: string; selectedResult?: string; @@ -42,7 +43,7 @@ export interface SettingsSearchRendererProps { } export default function SettingsSearchRenderer(props: SettingsSearchRendererProps) { - const { className, results, searchQuery, selectedResult, showResults } = props; + const { className, component, results, searchQuery, selectedResult, showResults } = props; const scrollableNodeRef = React.useRef(null); const selectedNodeRef = React.useRef<HTMLLIElement>(null); @@ -79,7 +80,7 @@ export default function SettingsSearchRenderer(props: SettingsSearchRendererProp <Link onClick={props.onClickOutside} onMouseEnter={() => props.onMouseOverResult(r.key)} - to={buildSettingLink(r)}> + to={buildSettingLink(r, component)}> <div className="settings-search-result-title display-flex-space-between"> <h3>{r.name || r.subCategory}</h3> </div> diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx index f6ea708a34d..d12e7c62b03 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx @@ -20,6 +20,7 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { KeyboardCodes } from '../../../../helpers/keycodes'; +import { mockComponent } from '../../../../helpers/mocks/component'; import { mockDefinition } from '../../../../helpers/mocks/settings'; import { mockRouter } from '../../../../helpers/testMocks'; import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils'; @@ -137,6 +138,14 @@ describe('instance', () => { }); }); +describe('project settings search', () => { + it('should load the correct definitions', () => { + const wrapper = shallowRender({ component: mockComponent(), definitions: [] }); + + expect(Object.keys(wrapper.instance().definitionsByKey)).toHaveLength(1); + }); +}); + function shallowRender(overrides: Partial<SettingsSearch['props']> = {}) { return shallow<SettingsSearch>( <SettingsSearch definitions={[mockDefinition()]} router={mockRouter()} {...overrides} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap index 682807a8995..9f54727d6a1 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap @@ -20,6 +20,31 @@ exports[`should render correctly: for project 1`] = ` > project_settings.page.description </div> + <withRouter(Connect(SettingsSearch)) + className="big-spacer-top" + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "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> </div> </header> @@ -27,13 +52,13 @@ exports[`should render correctly: for project 1`] = ` exports[`should render correctly: global 1`] = ` <header - className="top-bar-outer with-search" + className="top-bar-outer" > <div className="top-bar" > <div - className="top-bar-inner bordered-bottom big-padded-top padded-bottom with-search" + className="top-bar-inner bordered-bottom big-padded-top padded-bottom" > <h1 className="page-title" diff --git a/server/sonar-web/src/main/js/apps/settings/styles.css b/server/sonar-web/src/main/js/apps/settings/styles.css index 1055538f26f..fe68f2ba0f9 100644 --- a/server/sonar-web/src/main/js/apps/settings/styles.css +++ b/server/sonar-web/src/main/js/apps/settings/styles.css @@ -37,7 +37,7 @@ } #settings-page .top-bar-outer { - height: 80px; + height: 120px; } #settings-page .top-bar { @@ -51,13 +51,8 @@ #settings-page .top-bar-inner { max-width: 1280px; margin: 0 auto; - height: 80px; - box-sizing: border-box; -} - -#settings-page .top-bar-outer.with-search, -#settings-page .top-bar-inner.with-search { height: 120px; + box-sizing: border-box; } #settings-page .page-title, diff --git a/server/sonar-web/src/main/js/apps/settings/utils.ts b/server/sonar-web/src/main/js/apps/settings/utils.ts index 5589c5e532d..dcb442a7570 100644 --- a/server/sonar-web/src/main/js/apps/settings/utils.ts +++ b/server/sonar-web/src/main/js/apps/settings/utils.ts @@ -19,7 +19,7 @@ */ import { LocationDescriptor } from 'history'; import { hasMessage, translate } from '../../helpers/l10n'; -import { getGlobalSettingsUrl } from '../../helpers/urls'; +import { getGlobalSettingsUrl, getProjectSettingsUrl } from '../../helpers/urls'; import { AlmKeys } from '../../types/alm-settings'; import { Setting, SettingCategoryDefinition, SettingDefinition } from '../../types/settings'; @@ -168,9 +168,19 @@ export function isRealSettingKey(key: string) { ].includes(key); } -export function buildSettingLink(definition: SettingCategoryDefinition): LocationDescriptor { +export function buildSettingLink( + definition: SettingCategoryDefinition, + component?: T.Component +): LocationDescriptor { const { category, key } = definition; + if (component !== undefined) { + return { + ...getProjectSettingsUrl(component.key, category), + hash: `#${escape(key)}` + }; + } + const query: T.Dict<string> = {}; if (key.startsWith('sonar.auth.gitlab')) { @@ -187,6 +197,21 @@ export function buildSettingLink(definition: SettingCategoryDefinition): Locatio }; } +export const ADDITIONAL_PROJECT_SETTING_DEFINITIONS: SettingCategoryDefinition[] = [ + { + name: 'DevOps Platform Integration', + description: ` + Display your Quality Gate status directly in your DevOps Platform. + Each DevOps Platform instance must be configured globally first, and given a unique name. Pick the instance your project is hosted on. + `, + category: 'pull_request_decoration_binding', + key: ``, + fields: [], + options: [], + subCategory: '' + } +]; + export const ADDITIONAL_SETTING_DEFINITIONS: SettingCategoryDefinition[] = [ { name: 'Default New Code behavior', |