selected: '',
view: utils.DEFAULT_VIEW
});
- expect(utils.parseQuery({ metric: 'foo', selected: 'bar', view: 'tree' })).toEqual({
+ expect(
+ utils.parseQuery({ metric: 'foo', selected: 'bar', view: 'tree', asc: 'false' })
+ ).toEqual({
metric: 'foo',
selected: 'bar',
- view: 'tree'
+ view: 'tree',
+ asc: false
});
});
rootComponent={component}
router={this.props.router}
selected={query.selected}
+ asc={query.asc}
updateQuery={this.updateQuery}
view={query.view}
/>
rootComponent: ComponentMeasure;
router: InjectedRouter;
selected?: string;
+ asc?: boolean;
updateQuery: (query: Partial<Query>) => void;
view: MeasurePageView;
}
}
fetchComponentTree = () => {
- const { metricKeys, opts, strategy } = this.getComponentRequestParams(
- this.props.view,
- this.props.requestedMetric
- );
- const componentKey = this.props.selected || this.props.rootComponent.key;
- const baseComponentMetrics = [this.props.requestedMetric.key];
- if (this.props.requestedMetric.key === MetricKey.ncloc) {
+ const { asc, branchLike, metrics, requestedMetric, rootComponent, selected, view } = this.props;
+ // if asc is undefined we dont want to pass it inside options
+ const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, requestedMetric, {
+ ...(asc !== undefined && { asc })
+ });
+ const componentKey = selected || rootComponent.key;
+ const baseComponentMetrics = [requestedMetric.key];
+ if (requestedMetric.key === MetricKey.ncloc) {
baseComponentMetrics.push('ncloc_language_distribution');
}
Promise.all([
getMeasures({
component: componentKey,
metricKeys: baseComponentMetrics.join(),
- ...getBranchLikeQuery(this.props.branchLike)
+ ...getBranchLikeQuery(branchLike)
})
]).then(([tree, measures]) => {
if (this.mounted) {
- const metric = tree.metrics.find(m => m.key === this.props.requestedMetric.key);
+ const metric = tree.metrics.find(m => m.key === requestedMetric.key);
const components = tree.components.map(component =>
- enhanceComponent(component, metric, this.props.metrics)
+ enhanceComponent(component, metric, metrics)
);
- const measure = measures.find(measure => measure.metric === this.props.requestedMetric.key);
- const secondaryMeasure = measures.find(
- measure => measure.metric !== this.props.requestedMetric.key
- );
+ const measure = measures.find(measure => measure.metric === requestedMetric.key);
+ const secondaryMeasure = measures.find(measure => measure.metric !== requestedMetric.key);
this.setState(({ selectedComponent }) => ({
baseComponent: tree.baseComponent,
};
fetchMoreComponents = () => {
- const { metrics, view } = this.props;
+ const { metrics, view, asc } = this.props;
const { baseComponent, metric, paging } = this.state;
if (!baseComponent || !paging || !metric) {
return;
}
+ // if asc is undefined we dont want to pass it inside options
const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, metric, {
- p: paging.pageIndex + 1
+ p: paging.pageIndex + 1,
+ ...(asc !== undefined && { asc })
});
this.setState({ loadingMoreComponents: true });
getComponentTree(strategy, baseComponent.key, metricKeys, opts).then(
scrollToElement(element, { topOffset: offset - 100, bottomOffset: offset, smooth: true });
};
+ getDefaultShowBestMeasures() {
+ const { asc, view } = this.props;
+ if (asc !== undefined && view === 'list') {
+ return true;
+ } else if (view === 'tree') {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
renderMeasure() {
const { view } = this.props;
const { metric } = this.state;
<FilesView
branchLike={this.props.branchLike}
components={this.state.components}
- defaultShowBestMeasures={view === 'tree'}
+ defaultShowBestMeasures={this.getDefaultShowBestMeasures()}
fetchMore={this.fetchMoreComponents}
handleOpen={this.onOpenComponent}
handleSelect={this.onSelectComponent}
expect(wrapper).toMatchSnapshot();
});
+it('should render correctly when asc prop is defined', async () => {
+ const wrapper = shallowRender({ asc: true });
+ expect(wrapper.type()).toBeNull();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render correctly when view prop is tree', async () => {
+ const wrapper = shallowRender({ view: 'tree' });
+ expect(wrapper.type()).toBeNull();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
it('should render correctly for a file', async () => {
(getComponentTree as jest.Mock).mockResolvedValueOnce({
paging: { pageIndex: 1, pageSize: 500, total: 0 },
});
});
+it('should test fetchMoreComponents to work correctly', async () => {
+ (getComponentTree as jest.Mock).mockResolvedValueOnce({
+ paging: { pageIndex: 12, pageSize: 500, total: 0 },
+ baseComponent: mockComponentMeasure(false),
+ components: [],
+ metrics: [METRICS.bugs]
+ });
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ wrapper.instance().fetchMoreComponents();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should test getComponentRequestParams response for different arguments', () => {
+ const wrapper = shallowRender({ asc: false });
+ const metric = {
+ direction: -1,
+ key: 'new_reliability_rating'
+ };
+ const reqParamsList = {
+ metricKeys: ['new_reliability_rating'],
+ opts: {
+ additionalFields: 'metrics',
+ asc: true,
+ metricPeriodSort: 1,
+ metricSort: 'new_reliability_rating',
+ metricSortFilter: 'withMeasuresOnly',
+ ps: 500,
+ s: 'metricPeriod'
+ },
+ strategy: 'leaves'
+ };
+ expect(wrapper.instance().getComponentRequestParams('list', metric, { asc: true })).toEqual(
+ reqParamsList
+ );
+ // when options.asc is not passed the opts.asc will take the default value
+ reqParamsList.opts.asc = false;
+ expect(wrapper.instance().getComponentRequestParams('list', metric, {})).toEqual(reqParamsList);
+
+ const reqParamsTreeMap = {
+ metricKeys: ['new_reliability_rating', 'new_lines'],
+ opts: {
+ additionalFields: 'metrics',
+ asc: true,
+ metricPeriodSort: 1,
+ metricSort: 'new_lines',
+ metricSortFilter: 'withMeasuresOnly',
+ ps: 500,
+ s: 'metricPeriod'
+ },
+ strategy: 'children'
+ };
+ expect(wrapper.instance().getComponentRequestParams('treemap', metric, { asc: true })).toEqual(
+ reqParamsTreeMap
+ );
+ // when options.asc is not passed the opts.asc will take the default value
+ reqParamsTreeMap.opts.asc = false;
+ expect(wrapper.instance().getComponentRequestParams('treemap', metric, {})).toEqual(
+ reqParamsTreeMap
+ );
+
+ const reqParamsTree = {
+ metricKeys: ['new_reliability_rating'],
+ opts: {
+ additionalFields: 'metrics',
+ asc: false,
+ ps: 500,
+ s: 'qualifier,name'
+ },
+ strategy: 'children'
+ };
+ expect(wrapper.instance().getComponentRequestParams('tree', metric, { asc: false })).toEqual(
+ reqParamsTree
+ );
+ // when options.asc is not passed the opts.asc will take the default value
+ reqParamsTree.opts.asc = true;
+ expect(wrapper.instance().getComponentRequestParams('tree', metric, {})).toEqual(reqParamsTree);
+});
+
function shallowRender(props: Partial<MeasureContent['props']> = {}) {
return shallow<MeasureContent>(
<MeasureContent
</div>
</div>
`;
+
+exports[`should render correctly when asc prop is defined 1`] = `
+<div
+ className="layout-page-main no-outline"
+>
+ <A11ySkipTarget
+ anchor="measures_main"
+ />
+ <div
+ className="layout-page-header-panel layout-page-main-header"
+ >
+ <div
+ className="layout-page-header-panel-inner layout-page-main-header-inner"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <MeasureContentHeader
+ left={
+ <Breadcrumbs
+ backToFirst={true}
+ className="text-ellipsis flex-1"
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ handleSelect={[Function]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ />
+ }
+ right={
+ <div
+ className="display-flex-center"
+ >
+ <React.Fragment>
+ <div>
+ component_measures.view_as
+ </div>
+ <MeasureViewSelect
+ className="measure-view-select spacer-left big-spacer-right"
+ handleViewChange={[Function]}
+ metric={
+ Object {
+ "bestValue": "0",
+ "custom": false,
+ "description": "Bugs",
+ "domain": "Reliability",
+ "hidden": false,
+ "higherValuesAreBetter": false,
+ "key": "bugs",
+ "name": "Bugs",
+ "qualitative": true,
+ "type": "INT",
+ }
+ }
+ view="list"
+ />
+ <PageActions
+ componentQualifier="TRK"
+ showShortcuts={true}
+ total={2}
+ />
+ </React.Fragment>
+ </div>
+ }
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="layout-page-main-inner measure-details-content"
+ >
+ <MeasureHeader
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ measureValue="12"
+ metric={
+ Object {
+ "bestValue": "0",
+ "custom": false,
+ "description": "Bugs",
+ "domain": "Reliability",
+ "hidden": false,
+ "higherValuesAreBetter": false,
+ "key": "bugs",
+ "name": "Bugs",
+ "qualitative": true,
+ "type": "INT",
+ }
+ }
+ />
+ <FilesView
+ components={
+ Array [
+ Object {
+ "key": "foo:src/index.tsx",
+ "leak": undefined,
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "leak": undefined,
+ "metric": Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ "value": "1",
+ },
+ ],
+ "name": "index.tsx",
+ "path": "src/index.tsx",
+ "qualifier": "FIL",
+ "value": "1",
+ },
+ ]
+ }
+ defaultShowBestMeasures={true}
+ fetchMore={[Function]}
+ handleOpen={[Function]}
+ handleSelect={[Function]}
+ loadingMore={false}
+ metric={
+ Object {
+ "bestValue": "0",
+ "custom": false,
+ "description": "Bugs",
+ "domain": "Reliability",
+ "hidden": false,
+ "higherValuesAreBetter": false,
+ "key": "bugs",
+ "name": "Bugs",
+ "qualitative": true,
+ "type": "INT",
+ }
+ }
+ metrics={
+ Object {
+ "bugs": Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ }
+ }
+ paging={
+ Object {
+ "pageIndex": 1,
+ "pageSize": 500,
+ "total": 2,
+ }
+ }
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ view="list"
+ />
+ </div>
+</div>
+`;
+
+exports[`should render correctly when view prop is tree 1`] = `
+<div
+ className="layout-page-main no-outline"
+>
+ <A11ySkipTarget
+ anchor="measures_main"
+ />
+ <div
+ className="layout-page-header-panel layout-page-main-header"
+ >
+ <div
+ className="layout-page-header-panel-inner layout-page-main-header-inner"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <MeasureContentHeader
+ left={
+ <Breadcrumbs
+ backToFirst={false}
+ className="text-ellipsis flex-1"
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ handleSelect={[Function]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ />
+ }
+ right={
+ <div
+ className="display-flex-center"
+ >
+ <React.Fragment>
+ <div>
+ component_measures.view_as
+ </div>
+ <MeasureViewSelect
+ className="measure-view-select spacer-left big-spacer-right"
+ handleViewChange={[Function]}
+ metric={
+ Object {
+ "bestValue": "0",
+ "custom": false,
+ "description": "Bugs",
+ "domain": "Reliability",
+ "hidden": false,
+ "higherValuesAreBetter": false,
+ "key": "bugs",
+ "name": "Bugs",
+ "qualitative": true,
+ "type": "INT",
+ }
+ }
+ view="tree"
+ />
+ <PageActions
+ componentQualifier="TRK"
+ showShortcuts={true}
+ total={2}
+ />
+ </React.Fragment>
+ </div>
+ }
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="layout-page-main-inner measure-details-content"
+ >
+ <MeasureHeader
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ measureValue="12"
+ metric={
+ Object {
+ "bestValue": "0",
+ "custom": false,
+ "description": "Bugs",
+ "domain": "Reliability",
+ "hidden": false,
+ "higherValuesAreBetter": false,
+ "key": "bugs",
+ "name": "Bugs",
+ "qualitative": true,
+ "type": "INT",
+ }
+ }
+ />
+ <FilesView
+ components={
+ Array [
+ Object {
+ "key": "foo:src/index.tsx",
+ "leak": undefined,
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "leak": undefined,
+ "metric": Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ "value": "1",
+ },
+ ],
+ "name": "index.tsx",
+ "path": "src/index.tsx",
+ "qualifier": "FIL",
+ "value": "1",
+ },
+ ]
+ }
+ defaultShowBestMeasures={true}
+ fetchMore={[Function]}
+ handleOpen={[Function]}
+ handleSelect={[Function]}
+ loadingMore={false}
+ metric={
+ Object {
+ "bestValue": "0",
+ "custom": false,
+ "description": "Bugs",
+ "domain": "Reliability",
+ "hidden": false,
+ "higherValuesAreBetter": false,
+ "key": "bugs",
+ "name": "Bugs",
+ "qualitative": true,
+ "type": "INT",
+ }
+ }
+ metrics={
+ Object {
+ "bugs": Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ }
+ }
+ paging={
+ Object {
+ "pageIndex": 1,
+ "pageSize": 500,
+ "total": 2,
+ }
+ }
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ view="tree"
+ />
+ </div>
+</div>
+`;
+
+exports[`should test fetchMoreComponents to work correctly 1`] = `
+<div
+ className="layout-page-main no-outline"
+>
+ <A11ySkipTarget
+ anchor="measures_main"
+ />
+ <div
+ className="layout-page-header-panel layout-page-main-header"
+ >
+ <div
+ className="layout-page-header-panel-inner layout-page-main-header-inner"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <MeasureContentHeader
+ left={
+ <Breadcrumbs
+ backToFirst={true}
+ className="text-ellipsis flex-1"
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ handleSelect={[Function]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ />
+ }
+ right={
+ <div
+ className="display-flex-center"
+ >
+ <React.Fragment>
+ <div>
+ component_measures.view_as
+ </div>
+ <MeasureViewSelect
+ className="measure-view-select spacer-left big-spacer-right"
+ handleViewChange={[Function]}
+ metric={
+ Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ }
+ }
+ view="list"
+ />
+ <PageActions
+ componentQualifier="TRK"
+ showShortcuts={true}
+ total={0}
+ />
+ </React.Fragment>
+ </div>
+ }
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="layout-page-main-inner measure-details-content"
+ >
+ <MeasureHeader
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ measureValue="12"
+ metric={
+ Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ }
+ }
+ />
+ <FilesView
+ components={Array []}
+ defaultShowBestMeasures={false}
+ fetchMore={[Function]}
+ handleOpen={[Function]}
+ handleSelect={[Function]}
+ loadingMore={true}
+ metric={
+ Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ }
+ }
+ metrics={
+ Object {
+ "bugs": Object {
+ "domain": "Reliability",
+ "id": "1",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ }
+ }
+ paging={
+ Object {
+ "pageIndex": 12,
+ "pageSize": 500,
+ "total": 0,
+ }
+ }
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ view="list"
+ />
+ </div>
+</div>
+`;
Object {
"pathname": "/component_measures",
"query": Object {
+ "asc": undefined,
"branch": "develop",
"id": "foo",
"metric": "bugs",
Object {
"pathname": "/component_measures",
"query": Object {
+ "asc": undefined,
"id": "foo",
"metric": "bugs",
"selected": "foo",
Object {
"pathname": "/component_measures",
"query": Object {
+ "asc": undefined,
"branch": "develop",
"id": "foo",
"metric": "bugs",
Object {
"pathname": "/component_measures",
"query": Object {
+ "asc": undefined,
"id": "foo",
"metric": "bugs",
"selected": "foo",
Object {
"pathname": "/component_measures",
"query": Object {
+ "asc": undefined,
"branch": "develop",
"id": "foo",
"metric": "bugs",
Object {
"pathname": "/component_measures",
"query": Object {
+ "asc": undefined,
"id": "foo",
"metric": "bugs",
"selected": "foo",
Object {
"pathname": "/component_measures",
"query": Object {
+ "asc": undefined,
"id": "foo",
"metric": "bugs",
"selected": "foo:src/index.tsx",
import { isBranch, isPullRequest } from '../../helpers/branch-like';
import { getLocalizedMetricName } from '../../helpers/l10n';
import { getDisplayMetrics, isDiffMetric } from '../../helpers/measures';
-import { cleanQuery, parseAsString, serializeString } from '../../helpers/query';
+import {
+ cleanQuery,
+ parseAsOptionalBoolean,
+ parseAsString,
+ serializeString
+} from '../../helpers/query';
import { BranchLike } from '../../types/branch-like';
import { ComponentQualifier } from '../../types/component';
import { MeasurePageView } from '../../types/measures';
metric: string;
selected?: string;
view: MeasurePageView;
+ asc?: boolean;
}
export const parseQuery = memoize(
return {
metric,
selected: parseAsString(urlQuery['selected']),
- view: parseView(metric, urlQuery['view'])
+ view: parseView(metric, urlQuery['view']),
+ asc: parseAsOptionalBoolean(urlQuery['asc'])
};
}
);
Object {
"pathname": "/component_measures",
"query": Object {
+ "asc": undefined,
"id": "project123",
"metric": "other",
"view": "list",
query: { id: COMPLEX_COMPONENT_KEY, metric: METRIC }
});
});
+
+ it('should add asc param only when its list view', () => {
+ expect(
+ getComponentDrilldownUrl({ componentKey: SIMPLE_COMPONENT_KEY, metric: METRIC, asc: false })
+ ).toEqual({
+ pathname: '/component_measures',
+ query: { id: SIMPLE_COMPONENT_KEY, metric: METRIC }
+ });
+
+ expect(
+ getComponentDrilldownUrl({
+ componentKey: SIMPLE_COMPONENT_KEY,
+ metric: METRIC,
+ listView: true,
+ asc: false
+ })
+ ).toEqual({
+ pathname: '/component_measures',
+ query: { id: SIMPLE_COMPONENT_KEY, metric: METRIC, asc: 'false', view: 'list' }
+ });
+ });
});
describe('#getComponentDrilldownUrlWithSelection', () => {
import { Dict, HomePage } from '../types/types';
import { getBranchLikeQuery, isBranch, isMainBranch, isPullRequest } from './branch-like';
import { IS_SSR } from './browser';
+import { serializeOptionalBoolean } from './query';
import { getBaseUrl } from './system';
export interface Location {
selectionKey?: string;
treemapView?: boolean;
listView?: boolean;
+ asc?: boolean;
}): Location {
- const { componentKey, metric, branchLike, selectionKey, treemapView, listView } = options;
+ const { componentKey, metric, branchLike, selectionKey, treemapView, listView, asc } = options;
const query: Query = { id: componentKey, metric, ...getBranchLikeQuery(branchLike) };
if (treemapView) {
query.view = 'treemap';
}
if (listView) {
query.view = 'list';
+ query.asc = serializeOptionalBoolean(asc);
}
if (selectionKey) {
query.selected = selectionKey;