Skip to content

Commit

Permalink
Add fileUploader
Browse files Browse the repository at this point in the history
Signed-off-by: Yuri Roncella <[email protected]>
  • Loading branch information
Yuri Roncella committed Feb 8, 2019
1 parent 0f71029 commit 2301364
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 3 deletions.
7 changes: 7 additions & 0 deletions packages/jaeger-ui/src/actions/jaeger-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import { createAction } from 'redux-actions';
import JaegerAPI from '../api/jaeger';
import fileReader from '../utils/fileReader';

export const fetchTrace = createAction(
'@JAEGER_API/FETCH_TRACE',
Expand Down Expand Up @@ -50,3 +51,9 @@ export const fetchServiceOperations = createAction(
export const fetchDependencies = createAction('@JAEGER_API/FETCH_DEPENDENCIES', () =>
JaegerAPI.fetchDependencies()
);

export const loadTracesFromFile = createAction(
'@JAEGER_API/SEARCH_TRACES',
fileList => fileReader.readJSONFile(fileList),
fileList => ({ fileList })
);
20 changes: 20 additions & 0 deletions packages/jaeger-ui/src/actions/jaeger-api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import isPromise from 'is-promise';

import * as jaegerApiActions from './jaeger-api';
import JaegerAPI from '../api/jaeger';
import fileReader from '../utils/fileReader';

it('@JAEGER_API/FETCH_TRACE should fetch the trace by id', () => {
const api = JaegerAPI;
Expand Down Expand Up @@ -114,3 +115,22 @@ it('@JAEGER_API/FETCH_SERVICE_OPERATIONS should call the JaegerAPI', () => {
expect(called.verify()).toBeTruthy();
mock.restore();
});

it('loadTracesFromFile should return a promise', () => {
const fileList = { data: {}, filename: 'whatever' };

const { payload } = jaegerApiActions.loadTracesFromFile(fileList);
expect(isPromise(payload)).toBeTruthy();
});

it('loadTracesFromFile should call readJSONFile', () => {
const fileList = { data: {}, filename: 'whatever' };
const mock = sinon.mock(fileReader);
const called = mock
.expects('readJSONFile')
.once()
.withExactArgs(fileList);
jaegerApiActions.loadTracesFromFile(fileList);
expect(called.verify()).toBeTruthy();
mock.restore();
});
49 changes: 49 additions & 0 deletions packages/jaeger-ui/src/components/SearchTracePage/FileUploader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2017 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 { Upload, Icon } from 'antd';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import * as jaegerApiActions from '../../actions/jaeger-api';

const Dragger = Upload.Dragger;

export function FileUploaderImpl(props) {
const { loadTracesFromFile } = props;
return (
<Dragger accept=".json" customRequest={loadTracesFromFile}>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">Support a JSON file containig one or more traces.</p>
</Dragger>
);
}

FileUploaderImpl.propTypes = {
loadTracesFromFile: PropTypes.func.isRequired,
};

function mapDispatchToProps(dispatch) {
const { loadTracesFromFile } = bindActionCreators(jaegerApiActions, dispatch);
return {
loadTracesFromFile,
};
}

export default connect(null, mapDispatchToProps)(FileUploaderImpl);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2017 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 React from 'react';
import { mount } from 'enzyme';

import { FileUploaderImpl as FileUploader } from './FileUploader';

describe('<FileUploader />', () => {
let wrapper;

beforeEach(() => {
wrapper = mount(<FileUploader />);
});

it('does not explode', () => {
expect(wrapper).toBeDefined();
});
});
15 changes: 12 additions & 3 deletions packages/jaeger-ui/src/components/SearchTracePage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/* eslint-disable react/require-default-props */

import React, { Component } from 'react';
import { Col, Row } from 'antd';
import { Col, Row, Tabs } from 'antd';
import PropTypes from 'prop-types';
import queryString from 'query-string';
import { connect } from 'react-redux';
Expand All @@ -34,10 +34,13 @@ import { fetchedState } from '../../constants';
import { sortTraces } from '../../model/search';
import getLastXformCacher from '../../utils/get-last-xform-cacher';
import { stripEmbeddedState } from '../../utils/embedded-url';
import FileUploader from './FileUploader';

import './index.css';
import JaegerLogo from '../../img/jaeger-logo.svg';

const TabPane = Tabs.TabPane;

// export for tests
export class SearchTracePageImpl extends Component {
componentDidMount() {
Expand Down Expand Up @@ -95,8 +98,14 @@ export class SearchTracePageImpl extends Component {
{!embedded && (
<Col span={6} className="SearchTracePage--column">
<div className="SearchTracePage--find">
<h2>Find Traces</h2>
{!loadingServices && services ? <SearchForm services={services} /> : <LoadingIndicator />}
<Tabs size="large">
<TabPane tab="Find Traces" key="searchForm">
{!loadingServices && services ? <SearchForm services={services} /> : <LoadingIndicator />}
</TabPane>
<TabPane tab="Load from File" key="fileUploader">
<FileUploader />
</TabPane>
</Tabs>
</div>
</Col>
)}
Expand Down
33 changes: 33 additions & 0 deletions packages/jaeger-ui/src/utils/fileReader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2017 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.

const fileReader = {
readJSONFile(fileList) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(reader.result);
};
reader.onerror = () => {
reject();
};
reader.onabort = () => {
reject();
};
reader.readAsText(fileList.file);
}).then(result => JSON.parse(result));
},
};

export default fileReader;
58 changes: 58 additions & 0 deletions packages/jaeger-ui/src/utils/fileReader.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) 2017 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 isPromise from 'is-promise';
import sinon from 'sinon';

import fileReader from './fileReader.js';

it('readJSONFile returns a promise', () => {
const fileList = { data: {}, filename: 'whatever' };

const promise = fileReader.readJSONFile(fileList);
expect(isPromise(promise)).toBeTruthy();
});

it('readJSONFile fails to load a fail', async () => {
expect.assertions(1);
const fileList = { data: {}, filename: 'whatever' };
try {
await fileReader.readJSONFile(fileList);
} catch (e) {
expect(true).toBeTruthy();
}
});

it('readJSONFile fails when fileList is wrong', async () => {
expect.assertions(2);

const mock = sinon.mock(window);
const called = mock.expects('FileReader').once();
const fileList = {
action: '',
filename: 'file',
file: { uid: '1234' },
data: {},
headers: {},
withCredentials: false,
};

try {
await fileReader.readJSONFile(fileList);
} catch (e) {
expect(true).toBeTruthy();
expect(called.verify()).toBeTruthy();
}
mock.restore();
});

0 comments on commit 2301364

Please sign in to comment.