aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2022-01-25 17:40:27 +0100
committersonartech <sonartech@sonarsource.com>2022-01-27 20:03:04 +0000
commit0a563e3e59e9802090424591c66f623baeeb3a2f (patch)
treec69b4715080831bc464fd6a410b2bb11c7906c92
parent1a2f7a181ffb7fb62b9d6e7c672462a84e2fda84 (diff)
downloadsonarqube-0a563e3e59e9802090424591c66f623baeeb3a2f.tar.gz
sonarqube-0a563e3e59e9802090424591c66f623baeeb3a2f.zip
SONAR-15464 project settings search
-rw-r--r--server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts18
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap29
-rw-r--r--server/sonar-web/src/main/js/apps/settings/styles.css9
-rw-r--r--server/sonar-web/src/main/js/apps/settings/utils.ts29
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',