Skip to content

Commit

Permalink
SCORE-12805: Tracing: Exporting of search results
Browse files Browse the repository at this point in the history
- add a button plus download function, tests
(Resolves jaegertracing#663)
  • Loading branch information
Katarzyna-B committed Mar 15, 2023
1 parent 6d281e9 commit 75555ff
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2023 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


import * as React from "react";
import {Button} from "antd";
import {shallow} from "enzyme";
import DownloadResults from "./DownloadResults";

describe('DownloadResults button', () => {

let wrapper;
const getBtn = (btnIndex = 0) => wrapper.find(Button).at(btnIndex);
const getLabel = (btnIndex = 0) => getBtn(btnIndex).prop('children');
const props = {
onDownloadResultsClicked: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
wrapper = shallow(<DownloadResults {...props} />);
});

it('when renders then correct label is showing', () => {
expect(getLabel()).toBe('Download Results');
});

it('when click then call download results function', () => {
expect(props.onDownloadResultsClicked).toHaveBeenCalledTimes(0);

getBtn(0).simulate('click', {});
expect(props.onDownloadResultsClicked).toHaveBeenCalledTimes(1);
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2023 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import * as React from 'react';
import {Button} from 'antd';

type Props = {
onDownloadResultsClicked: () => void;
};


export default function DownloadResults(props: Props) {
const {onDownloadResultsClicked} = props;
const toggleBtn = (
<Button className="ub-ml2" htmlType="button" onClick={onDownloadResultsClicked}>
Download Results
</Button>
);
return (
<>
{toggleBtn}
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,24 @@ import ScatterPlot from './ScatterPlot';
import { getUrl } from '../url';
import LoadingIndicator from '../../common/LoadingIndicator';
import SearchResultsDDG from '../../DeepDependencies/traces';
import DownloadResults from "./DownloadResults";

describe('<SearchResults>', () => {
const searchParam = 'view';
const otherParam = 'param';
const otherValue = 'value';
const otherSearch = `?${otherParam}=${otherValue}`;
let wrapper;
let traces;
let tracesToDownload;
let props;

beforeEach(() => {
traces = [
{ traceID: 'a', spans: [], processes: {} },
{ traceID: 'b', spans: [], processes: {} },
];
tracesToDownload = traces;
props = {
diffCohort: [],
goToTrace: () => {},
Expand All @@ -44,6 +51,7 @@ describe('<SearchResults>', () => {
maxTraceDuration: 1,
queryOfResults: {},
traces,
tracesToDownload,
};
wrapper = shallow(<SearchResults {...props} />);
});
Expand Down Expand Up @@ -108,20 +116,16 @@ describe('<SearchResults>', () => {
});

describe('ddg', () => {
const searchParam = 'view';
const viewDdg = 'ddg';
const viewTraces = 'traces';
const search = `${searchParam}=${viewDdg}`;
const search = searchParam + `=${viewDdg}`;
let trackAltViewSpy;

beforeAll(() => {
trackAltViewSpy = jest.spyOn(track, 'trackAltView');
});

it('updates url to view ddg and back and back again - and tracks changes', () => {
const otherParam = 'param';
const otherValue = 'value';
const otherSearch = `?${otherParam}=${otherValue}`;
const push = jest.fn();
wrapper.setProps({ history: { push }, location: { search: otherSearch } });

Expand Down Expand Up @@ -154,5 +158,33 @@ describe('<SearchResults>', () => {
expect(wrapper.find(ScatterPlot).length).toBe(0);
});
});

describe('DownloadResults', () => {
it('shows DownloadResults when view is not ddg', () => {
const view = 'traces';
wrapper.setProps({ location: { search: `${otherSearch}&${searchParam}=${view}` } });
expect(wrapper.find(DownloadResults).length).toBe(1);
});

it('does not show DownloadResults when view is ddg', () => {
const view = 'ddg';
wrapper.setProps({ location: { search: `${otherSearch}&${searchParam}=${view}` } });
expect(wrapper.find(DownloadResults).length).toBe(0);
});

it('when click on DownloadResults then call download function', () => {
URL.createObjectURL = jest.fn();
URL.revokeObjectURL = jest.fn();
const file = new Blob([JSON.stringify(tracesToDownload)], {type : "application/json"});
const view = 'traces';
wrapper.setProps({ location: { search: `${otherSearch}&${searchParam}=${view}` } });

const download = wrapper.find(DownloadResults).prop('onDownloadResultsClicked');
download();
expect(URL.createObjectURL).toBeCalledTimes(1);
expect(URL.createObjectURL).toBeCalledWith(file);
expect(URL.revokeObjectURL).toBeCalledTimes(1);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Field, formValueSelector, reduxForm } from 'redux-form';
import queryString from 'query-string';

import AltViewOptions from './AltViewOptions';
import DownloadResults from './DownloadResults';
import DiffSelection from './DiffSelection';
import * as markers from './index.markers';
import { EAltViewActions, trackAltView } from './index.track';
Expand All @@ -39,7 +40,7 @@ import reduxFormFieldAdapter from '../../../utils/redux-form-field-adapter';

import { FetchedTrace } from '../../../types';
import { SearchQuery } from '../../../types/search';
import { KeyValuePair, Trace } from '../../../types/trace';
import {KeyValuePair, Trace, TraceData} from '../../../types/trace';

import './index.css';

Expand All @@ -59,6 +60,7 @@ type SearchResultsProps = {
skipMessage?: boolean;
spanLinks?: Record<string, string> | undefined;
traces: Trace[];
tracesToDownload: TraceData[];
};

const Option = Select.Option;
Expand Down Expand Up @@ -110,6 +112,17 @@ export class UnconnectedSearchResults extends React.PureComponent<SearchResultsP
history.push(getUrl({ ...urlState, view }));
};

onDownloadResultsClicked = () => {
const file = new Blob([JSON.stringify(this.props.tracesToDownload)], {type : "application/json"});
const element = document.createElement("a");
element.href = URL.createObjectURL(file);
element.download = "traces-" + Date.now() + ".json";
document.body.appendChild(element);
element.click();
URL.revokeObjectURL(element.href)
element.remove()
};

render() {
const {
diffCohort,
Expand Down Expand Up @@ -181,6 +194,7 @@ export class UnconnectedSearchResults extends React.PureComponent<SearchResultsP
{traces.length} Trace{traces.length > 1 && 's'}
</h2>
{traceResultsView && <SelectSort />}
{traceResultsView && <DownloadResults onDownloadResultsClicked={this.onDownloadResultsClicked}/>}
<AltViewOptions traceResultsView={traceResultsView} onDdgViewClicked={this.onDdgViewClicked} />
{showStandaloneLink && (
<Link
Expand Down
10 changes: 8 additions & 2 deletions packages/jaeger-ui/src/components/SearchTracePage/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export class SearchTracePageImpl extends Component {
maxTraceDuration,
services,
traceResults,
traceResultsToDownload,
queryOfResults,
loadJsonTraces,
urlQueryParams,
Expand Down Expand Up @@ -141,6 +142,7 @@ export class SearchTracePageImpl extends Component {
skipMessage={isHomepage}
spanLinks={urlQueryParams && urlQueryParams.spanLinks}
traces={traceResults}
tracesToDownload={traceResultsToDownload}
/>
)}
{showLogo && (
Expand All @@ -161,6 +163,8 @@ SearchTracePageImpl.propTypes = {
// eslint-disable-next-line react/forbid-prop-types
traceResults: PropTypes.array,
// eslint-disable-next-line react/forbid-prop-types
traceResultsToDownload: PropTypes.array,
// eslint-disable-next-line react/forbid-prop-types
diffCohort: PropTypes.array,
cohortAddTrace: PropTypes.func,
cohortRemoveTrace: PropTypes.func,
Expand Down Expand Up @@ -200,7 +204,7 @@ SearchTracePageImpl.propTypes = {
};

const stateTraceXformer = memoizeOne(stateTrace => {
const { traces: traceMap, search } = stateTrace;
const { traces: traceMap, tracesToDownload, search } = stateTrace;
const { query, results, state, error: traceError } = search;

const loadingTraces = state === fetchedState.LOADING;
Expand All @@ -209,7 +213,7 @@ const stateTraceXformer = memoizeOne(stateTrace => {
null,
traces.map(tr => tr.duration)
);
return { traces, maxDuration, traceError, loadingTraces, query };
return { traces, tracesToDownload, maxDuration, traceError, loadingTraces, query };
});

const stateTraceDiffXformer = memoizeOne((stateTrace, stateTraceDiff) => {
Expand Down Expand Up @@ -248,6 +252,7 @@ export function mapStateToProps(state) {
const {
query: queryOfResults,
traces,
tracesToDownload,
maxDuration,
traceError,
loadingTraces,
Expand All @@ -272,6 +277,7 @@ export function mapStateToProps(state) {
loadingTraces,
services,
traceResults,
traceResultsToDownload:tracesToDownload,
errors: errors.length ? errors : null,
maxTraceDuration: maxDuration,
sortTracesBy: sortBy,
Expand Down
10 changes: 9 additions & 1 deletion packages/jaeger-ui/src/components/SearchTracePage/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,22 @@ describe('<SearchTracePage>', () => {
const queryOfResults = {};
let wrapper;
let traceResults;
let traceResultsToDownload;
let props;

beforeEach(() => {
traceResults = [
{ traceID: 'a', spans: [], processes: {} },
{ traceID: 'b', spans: [], processes: {} },
];
traceResultsToDownload = [
{ traceID: 'a', spans: [], processes: {} },
{ traceID: 'b', spans: [], processes: {} },
];
props = {
queryOfResults,
traceResults,
traceResultsToDownload,
diffCohort: [],
isHomepage: false,
loadingServices: false,
Expand Down Expand Up @@ -169,6 +175,7 @@ describe('mapStateToProps()', () => {
traces: {
[trace.traceID]: { id: trace.traceID, data: trace, state: fetchedState.DONE },
},
tracesToDownload: [trace],
};
const stateServices = {
loading: false,
Expand All @@ -185,10 +192,11 @@ describe('mapStateToProps()', () => {
services: stateServices,
};

const { maxTraceDuration, traceResults, diffCohort, numberOfTraceResults, location, ...rest } =
const { maxTraceDuration, traceResults, traceResultsToDownload, diffCohort, numberOfTraceResults, location, ...rest } =
mapStateToProps(state);
expect(traceResults).toHaveLength(stateTrace.search.results.length);
expect(traceResults[0].traceID).toBe(trace.traceID);
expect(traceResultsToDownload[0].traceID).toBe(trace.traceID);
expect(maxTraceDuration).toBe(trace.duration);
expect(diffCohort).toHaveLength(state.traceDiff.cohort.length);
expect(diffCohort[0].id).toBe(trace.traceID);
Expand Down
5 changes: 3 additions & 2 deletions packages/jaeger-ui/src/reducers/trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ function searchDone(state, { meta, payload }) {
if (!_isEqual(state.search.query, meta.query)) {
return state;
}
const processed = payload.data.map(transformTraceData);
const payloadData = payload.data;
const processed = payloadData.map(transformTraceData);
const resultTraces = {};
const results = [];
for (let i = 0; i < processed.length; i++) {
Expand All @@ -114,7 +115,7 @@ function searchDone(state, { meta, payload }) {
}
const traces = { ...state.traces, ...resultTraces };
const search = { ...state.search, results, state: fetchedState.DONE };
return { ...state, search, traces };
return { ...state, search, traces, tracesToDownload:payloadData};
}

function searchErred(state, { meta, payload }) {
Expand Down
1 change: 1 addition & 0 deletions packages/jaeger-ui/src/reducers/trace.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ describe('search traces', () => {
state: fetchedState.DONE,
},
},
tracesToDownload: [trace],
search: {
query,
state: fetchedState.DONE,
Expand Down

0 comments on commit 75555ff

Please sign in to comment.