]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15857 Measure page should support ascending and descending sorting for rating...
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>
Wed, 26 Jan 2022 16:59:46 +0000 (17:59 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 31 Jan 2022 20:02:32 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureContent-test.tsx
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureContent-test.tsx.snap
server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentCell-test.tsx.snap
server/sonar-web/src/main/js/apps/component-measures/utils.ts
server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/DrilldownLink-test.tsx.snap
server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts
server/sonar-web/src/main/js/helpers/urls.ts

index 98a7ce7ad59c57b545e379bca902e9e1ebbc2194..fe29210691c38a4f48e923c2267642cbaaa24f49 100644 (file)
@@ -124,10 +124,13 @@ describe('parseQuery', () => {
       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
     });
   });
 
index 3c5cb08289ee559bf3c216cbd4bdf57c0100cdf7..4078382e7f97729b62315168ae3129cc67fec94e 100644 (file)
@@ -284,6 +284,7 @@ export class App extends React.PureComponent<Props, State> {
         rootComponent={component}
         router={this.props.router}
         selected={query.selected}
+        asc={query.asc}
         updateQuery={this.updateQuery}
         view={query.view}
       />
index 11c50d3ddb20b8e993140277e545b11f80ff953f..60b16b58b1f8e1d60161635f179fffa0177f0c8b 100644 (file)
@@ -64,6 +64,7 @@ interface Props {
   rootComponent: ComponentMeasure;
   router: InjectedRouter;
   selected?: string;
+  asc?: boolean;
   updateQuery: (query: Partial<Query>) => void;
   view: MeasurePageView;
 }
@@ -110,13 +111,14 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
   }
 
   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([
@@ -124,19 +126,17 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
       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,
@@ -159,13 +159,15 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
   };
 
   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(
@@ -283,6 +285,17 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
     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;
@@ -295,7 +308,7 @@ export default class MeasureContent extends React.PureComponent<Props, 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}
index 568ba3638f89b5d6b29bcc6137d5a1828f40f9ef..861f7d075acbd86a35708a1f4da38b00332f25ce 100644 (file)
@@ -93,6 +93,20 @@ it('should render correctly for a project', async () => {
   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 },
@@ -117,6 +131,85 @@ it('should correctly handle scrolling', () => {
   });
 });
 
+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
index 0a0d342d3e45c5652c75d4cb368f85da2597d5db..8446a01bac01b2dc85eab32bb47b1dd0dee30ef5 100644 (file)
@@ -313,3 +313,590 @@ exports[`should render correctly for a project 1`] = `
   </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>
+`;
index cc3d3f3ca0710a21483bd0d12e57c4737556b0ae..730e6e4fb06a8ecfc28c7baabc2d549668bc67e4 100644 (file)
@@ -16,6 +16,7 @@ exports[`should render correctly for a "APP" root component and a component with
         Object {
           "pathname": "/component_measures",
           "query": Object {
+            "asc": undefined,
             "branch": "develop",
             "id": "foo",
             "metric": "bugs",
@@ -65,6 +66,7 @@ exports[`should render correctly for a "APP" root component and a component with
         Object {
           "pathname": "/component_measures",
           "query": Object {
+            "asc": undefined,
             "id": "foo",
             "metric": "bugs",
             "selected": "foo",
@@ -110,6 +112,7 @@ exports[`should render correctly for a "TRK" root component and a component with
         Object {
           "pathname": "/component_measures",
           "query": Object {
+            "asc": undefined,
             "branch": "develop",
             "id": "foo",
             "metric": "bugs",
@@ -151,6 +154,7 @@ exports[`should render correctly for a "TRK" root component and a component with
         Object {
           "pathname": "/component_measures",
           "query": Object {
+            "asc": undefined,
             "id": "foo",
             "metric": "bugs",
             "selected": "foo",
@@ -191,6 +195,7 @@ exports[`should render correctly for a "VW" root component and a component with
         Object {
           "pathname": "/component_measures",
           "query": Object {
+            "asc": undefined,
             "branch": "develop",
             "id": "foo",
             "metric": "bugs",
@@ -240,6 +245,7 @@ exports[`should render correctly for a "VW" root component and a component with
         Object {
           "pathname": "/component_measures",
           "query": Object {
+            "asc": undefined,
             "id": "foo",
             "metric": "bugs",
             "selected": "foo",
@@ -285,6 +291,7 @@ exports[`should render correctly: default 1`] = `
         Object {
           "pathname": "/component_measures",
           "query": Object {
+            "asc": undefined,
             "id": "foo",
             "metric": "bugs",
             "selected": "foo:src/index.tsx",
index 3a071456b4f849b48311b5f19a9177105260ea91..34e88b9aae6acd2c8c443ea51ee5d39c52b7b12d 100644 (file)
@@ -22,7 +22,12 @@ import { enhanceMeasure } from '../../components/measure/utils';
 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';
@@ -217,6 +222,7 @@ export interface Query {
   metric: string;
   selected?: string;
   view: MeasurePageView;
+  asc?: boolean;
 }
 
 export const parseQuery = memoize(
@@ -225,7 +231,8 @@ export const parseQuery = memoize(
     return {
       metric,
       selected: parseAsString(urlQuery['selected']),
-      view: parseView(metric, urlQuery['view'])
+      view: parseView(metric, urlQuery['view']),
+      asc: parseAsOptionalBoolean(urlQuery['asc'])
     };
   }
 );
index 43494b4d2bff59793307121449633cca36cf27a6..cb653af58a135dff0706e091a2d278883fb12fe9 100644 (file)
@@ -8,6 +8,7 @@ exports[`should render correctly 1`] = `
     Object {
       "pathname": "/component_measures",
       "query": Object {
+        "asc": undefined,
         "id": "project123",
         "metric": "other",
         "view": "list",
index afa9a0ecca7c4053b7367aee4925c508b11b154d..63fc209c51f230a1e175b7b4e0dc4c46bb47915c 100644 (file)
@@ -142,6 +142,27 @@ describe('#getComponentDrilldownUrl', () => {
       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', () => {
index 8045f920430364f05b98700bfee73a356378eea9..e7aaa61ddbbb698ac60ead9d749b9d67598e256e 100644 (file)
@@ -27,6 +27,7 @@ import { SecurityStandard } from '../types/security';
 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 {
@@ -161,14 +162,16 @@ export function getComponentDrilldownUrl(options: {
   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;