Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/visualization #83

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the type should just be PPLService?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, missed this one.

}
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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not very familiar with the event loop, but would it be possible that this finishes before debounced component re-renders and test fails? since the delays are both 1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it looks like it would not fail since when setting the props, this action would be put into the event loop, then the following assertion statement would run immediately and pass. No matter what statement is actually picked by the main thread to run next (setting props to 'yall' or await act...), it would actually end up making sure the debounce component has changed its value to 'yall', before the last assertion runs. Since event handlers will be invoked in the same order that they were bound in.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it, looks good then

});
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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid the wild card import usage here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will change all of them

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