Skip to content

Commit

Permalink
Feature/visualization (opensearch-project#83)
Browse files Browse the repository at this point in the history
* resloved conflicts for rebasing

* added overall layout and render fields

* added two types of charts

* added config panel for vis

* removed unused files and for a quick demo

* add intial redux setup

* added initial reducer

* refactorings for redux

* minor code cleanup

* adjusted chart styling, added timespan selector

* added timestamp flag and checking for charts

* fixed sidebar field icon issue

* code cleanup

* license and minor changes

* changes for code review

* removed few comments
  • Loading branch information
mengweieric authored Aug 6, 2021
1 parent e9f1aed commit 2442f53
Show file tree
Hide file tree
Showing 80 changed files with 4,159 additions and 397 deletions.
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
"main": "index.ts",
"license": "Apache-2.0",
"scripts": {
"test:cypress": "cypress run"
"osd": "node ../../scripts/osd",
"build": "yarn plugin_helpers build",
"cypress:run": "cypress run",
"cypress:open": "cypress open",
"plugin_helpers": "node ../../scripts/plugin_helpers"
},
"engines": {
"node": "10.23.1",
"yarn": "^1.22.10"
"dependencies": {
"@reduxjs/toolkit": "^1.6.1",
"plotly.js-dist": "^2.2.0",
"react-plotly.js": "^2.5.1"
},
"dependencies": {},
"devDependencies": {
"cypress": "5.0.0"
}
Expand Down
14 changes: 12 additions & 2 deletions public/common/constants/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ export const SELECTED_FIELDS = 'selectedFields';
export const UNSELECTED_FIELDS = 'unselectedFields';
export const TAB_ID_TXT_PFX = 'query-panel-';
export const TAB_TITLE = 'New query';
export const TAB_CHART_TITLE = 'Charts';
export const TAB_CHART_TITLE = 'Visualizations';
export const TAB_EVENT_TITLE = 'Events';
export const TAB_EVENT_ID_TXT_PFX = 'main-content-events-';
export const TAB_CHART_ID_TXT_PFX = 'main-content-charts-';
export const TAB_CHART_ID_TXT_PFX = 'main-content-charts-';

// redux
export const SELECTED_QUERY_TAB = 'selectedQueryTab';
export const QUERY_TAB_IDS = 'queryTabIds';
export const NEW_SELECTED_QUERY_TAB = 'newSelectedQueryTab';
export const REDUX_EXPL_SLICE_QUERIES = 'queries';
export const REDUX_EXPL_SLICE_QUERY_RESULT = 'queryResults';
export const REDUX_EXPL_SLICE_FIELDS = 'fields';
export const REDUX_EXPL_SLICE_QUERY_TABS = 'queryTabs';

15 changes: 2 additions & 13 deletions public/common/types/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,9 @@ export interface IExplorerTabFields {

export interface IExplorerFields {
[SELECTED_FIELDS]: Array<IField>;
[UNSELECTED_FIELDS]: Array<IField>
[UNSELECTED_FIELDS]: Array<IField>;
}

export interface IExplorerProps {
tabId: string;
query: any;
explorerData: any;
explorerFields: any;
setSearchQuery: (query: string, tabId: string) => void;
querySearch: (tabId: string) => void;
addField: (field: IField, tabId: string) => void;
removeField: (field: IField, tabId: string) => void
}

export interface LogExplorer {

pplService: any;
}
196 changes: 78 additions & 118 deletions public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
* GitHub history for details.
*/

import React, { useState, useRef } from 'react';
import React from 'react';
import { Provider } from 'react-redux';
import _ from 'lodash';
import { I18nProvider } from '@osd/i18n/react';
import { HashRouter, Route, Switch } from 'react-router-dom';
import store from '../framework/redux/store';
import { CoreStart } from '../../../../src/core/public';
import { renderPageWithSidebar } from './common/side_nav';
import { Home as ApplicationAnalyticsHome } from './application_analytics/home';
Expand All @@ -21,137 +23,95 @@ import { Home as OperationalPanelsHome} from './operational_panels/home';
import { Home as EventExplorerHome } from './explorer/home';
import { LogExplorer } from './explorer/logExplorer';
import { observabilityTitle } from '../../common';
import {
ITabQueryResults,
ITabQueries,
IExplorerTabFields
} from '../common/types/explorer';
import {
TAB_ID_TXT_PFX,
RAW_QUERY,
SELECTED_FIELDS,
UNSELECTED_FIELDS
} from '../common/constants/explorer';

interface ObservabilityAppDeps {
CoreStart: CoreStart;
pplService: any
}

export const App = ({
CoreStart
CoreStart,
pplService,
}: ObservabilityAppDeps) => {

const { chrome, http } = CoreStart;

// event explorer states
const initialTabId: string = getTabId(TAB_ID_TXT_PFX);
const [tabIds, setTabIds] = useState<Array<string>>([initialTabId]);
const [queries, setQueries] = useState<ITabQueries>({
[initialTabId]: {
[RAW_QUERY]: ''
}
});
const [queryResults, setQueryResults] = useState<ITabQueryResults>({
[initialTabId]: {}
});
const [fields, setFields] = useState<IExplorerTabFields>({
[initialTabId]: {
[SELECTED_FIELDS]: [],
[UNSELECTED_FIELDS]: []
}
});
const curQueriesRef = useRef(queries);
const [curSelectedTabId, setCurSelectedTab] = useState<string>(initialTabId);

function getTabId (prefix: string) {
return _.uniqueId(prefix);
}

const parentBreadcrumb = {
text: observabilityTitle,
href: 'observability#/'
};

return (
<HashRouter>
<I18nProvider>
<>
<Switch>
<Route
exact
path={['/', '/application_analytics', '/application_analytics/home']}
render={(props) => {
chrome.setBreadcrumbs([
parentBreadcrumb,
{
text: 'Application analytics',
href: '#/application_analytics'
},
]);
return renderPageWithSidebar(<ApplicationAnalyticsHome />, 1);
} }
/>
<Route
path={['/trace_analytics', '/trace_analytics/home']}
render={(props) => {
chrome.setBreadcrumbs([
parentBreadcrumb,
{
text: 'Trace analytics',
href: '#/trace_analytics/home'
},
]);
return renderPageWithSidebar(<TraceAnalyticsHome />, 2) }
}
/>
<Route
exact
path={['/event/', '/event/home']}
render={(props) => {
chrome.setBreadcrumbs([
parentBreadcrumb,
{
text: 'Event analytics',
href: '#/event/home'
},
]);
return renderPageWithSidebar(<EventExplorerHome />, 3);
} }
/>
<Route
path={['/operational_panels', '/operational_panels/home']}
render={(props) => {
chrome.setBreadcrumbs([
parentBreadcrumb,
{
text: 'Operational panels',
href: '#/operational_panels/home'
},
]);
return renderPageWithSidebar(<OperationalPanelsHome />, 4);
} }
/>
<Route
exact
path='/event/explorer'
render={(props) => <LogExplorer
http={ http }
tabIds={ tabIds }
queries={ queries }
queryResults={ queryResults }
fields={ fields }
curQueriesRef={ curQueriesRef }
curSelectedTabId={ curSelectedTabId }
setTabIds={ setTabIds }
setQueries={ setQueries }
setQueryResults={ setQueryResults }
setFields={ setFields }
setCurSelectedTab={ setCurSelectedTab }
/> }
/>
</Switch>
</>
</I18nProvider>
</HashRouter>
<Provider store={ store }>
<HashRouter>
<I18nProvider>
<>
<Switch>
<Route
exact
path={['/', '/application_analytics', '/application_analytics/home']}
render={(props) => {
chrome.setBreadcrumbs([
parentBreadcrumb,
{
text: 'Application analytics',
href: '#/application_analytics'
},
]);
return renderPageWithSidebar(<ApplicationAnalyticsHome />, 1);
} }
/>
<Route
path={['/trace_analytics', '/trace_analytics/home']}
render={(props) => {
chrome.setBreadcrumbs([
parentBreadcrumb,
{
text: 'Trace analytics',
href: '#/trace_analytics/home'
},
]);
return renderPageWithSidebar(<TraceAnalyticsHome />, 2) }
}
/>
<Route
exact
path={['/event/', '/event/home']}
render={(props) => {
chrome.setBreadcrumbs([
parentBreadcrumb,
{
text: 'Event analytics',
href: '#/event/home'
},
]);
return renderPageWithSidebar(<EventExplorerHome />, 3);
} }
/>
<Route
path={['/operational_panels', '/operational_panels/home']}
render={(props) => {
chrome.setBreadcrumbs([
parentBreadcrumb,
{
text: 'Operational panels',
href: '#/operational_panels/home'
},
]);
return renderPageWithSidebar(<OperationalPanelsHome />, 4);
} }
/>
<Route
exact
path='/event/explorer'
render={(props) => <LogExplorer
http={ http }
pplService={ pplService }
/> }
/>
</Switch>
</>
</I18nProvider>
</HashRouter>
</Provider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

import React from 'react';
import { mountWithIntl as mount } from 'test_utils/enzyme_helpers';
import { debouncedComponent } from './debounced_component';
import { act } from 'react-dom/test-utils';

describe('debouncedComponent', () => {
test('immediately renders', () => {
const TestComponent = debouncedComponent(({ title }: { title: string }) => {
return <h1>{title}</h1>;
});
expect(mount(<TestComponent title="hoi" />).html()).toMatchInlineSnapshot(`"<h1>hoi</h1>"`);
});

test('debounces changes', async () => {
const TestComponent = debouncedComponent(({ title }: { title: string }) => {
return <h1>{title}</h1>;
}, 1);
const component = mount(<TestComponent title="there" />);
component.setProps({ title: 'yall' });
expect(component.text()).toEqual('there');
await act(async () => {
await new Promise((r) => setTimeout(r, 1));
});
expect(component.text()).toEqual('yall');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

import React, { useState, useMemo, useEffect, memo, FunctionComponent } from 'react';
import { debounce } from 'lodash';

/**
* debouncedComponent wraps the specified React component, returning a component which
* only renders once there is a pause in props changes for at least `delay` milliseconds.
* During the debounce phase, it will return the previously rendered value.
*/
export function debouncedComponent<TProps>(component: FunctionComponent<TProps>, delay = 256) {
const MemoizedComponent = (memo(component) as unknown) as FunctionComponent<TProps>;

return (props: TProps) => {
const [cachedProps, setCachedProps] = useState(props);
const debouncePropsChange = useMemo(() => debounce(setCachedProps, delay), [setCachedProps]);

// cancel debounced prop change if component has been unmounted in the meantime
useEffect(() => () => debouncePropsChange.cancel(), [debouncePropsChange]);
debouncePropsChange(props);

return React.createElement(MemoizedComponent, cachedProps);
};
}
12 changes: 12 additions & 0 deletions public/components/common/debounced_component/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

export * from './debounced_component';
1 change: 0 additions & 1 deletion public/components/common/seach/queryBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function QueryBar(props: IQueryBarProps) {
data-test-subj="search-bar-input-box"
value={ query[RAW_QUERY] }
onChange={(e) => {
console.log('changed value: ', e.target.value);
handleQueryChange(e.target.value);
}}
onSearch={() => {
Expand Down
1 change: 1 addition & 0 deletions public/components/common/seach/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const Search = (props: any) => {
actionItems.map((item) => {
return (
<EuiFlexItem
key={_.uniqueId('search-action-')}
className={ item.className ? item.className : "euiFlexItem--flexGrowZero"}
>
<EuiButton
Expand Down
Loading

0 comments on commit 2442f53

Please sign in to comment.