Skip to content

Commit

Permalink
Improve search response rendering; other minor fixes (opensearch-proj…
Browse files Browse the repository at this point in the history
…ect#523)


Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler authored Dec 11, 2024
1 parent 6040bb6 commit 72c32e6
Show file tree
Hide file tree
Showing 12 changed files with 419 additions and 186 deletions.
21 changes: 21 additions & 0 deletions common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,27 @@ export type SimulateIngestPipelineResponse = {

export type SearchHit = SimulateIngestPipelineDoc;

export type SearchResponse = {
took: number;
timed_out: boolean;
_shards: {
total: number;
successful: number;
skipped: number;
failed: number;
};
hits: {
total: {
value: number;
relation: string;
};
max_score: number;
hits: SearchHit[];
};
aggregations?: {};
ext?: {};
};

export type IndexResponse = {
indexName: string;
indexDetails: IndexConfiguration;
Expand Down
1 change: 1 addition & 0 deletions public/general_components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export { MultiSelectFilter } from './multi_select_filter';
export { ProcessorsTitle } from './processors_title';
export { ExperimentalBadge } from './experimental_badge';
export { QueryParamsList } from './query_params_list';
export * from './results';
export * from './service_card';
6 changes: 6 additions & 0 deletions public/general_components/results/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { Results } from './results';
77 changes: 77 additions & 0 deletions public/general_components/results/results.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState } from 'react';
import {
EuiPanel,
EuiFlexGroup,
EuiFlexItem,
EuiSmallButtonGroup,
} from '@elastic/eui';
import { SearchResponse } from '../../../common';
import { ResultsTable } from './results_table';
import { ResultsJSON } from './results_json';

interface ResultsProps {
response: SearchResponse;
}

enum VIEW {
HITS_TABLE = 'hits_table',
RAW_JSON = 'raw_json',
}

/**
* Basic component to view OpenSearch response results. Can view hits in a tabular format,
* or the raw JSON response.
*/
export function Results(props: ResultsProps) {
// selected view state
const [selectedView, setSelectedView] = useState<VIEW>(VIEW.HITS_TABLE);

return (
<EuiPanel
hasBorder={false}
hasShadow={false}
paddingSize="none"
style={{ height: '10vh', overflowY: 'scroll', overflowX: 'hidden' }}
>
<EuiFlexGroup
direction="column"
gutterSize="xs"
style={{ height: '100%' }}
>
<EuiFlexItem grow={false}>
<EuiSmallButtonGroup
legend="Choose how to view your data"
options={[
{
id: VIEW.HITS_TABLE,
label: 'Hits',
},
{
id: VIEW.RAW_JSON,
label: 'Raw JSON',
},
]}
idSelected={selectedView}
onChange={(id) => setSelectedView(id as VIEW)}
data-testid="resultsToggleButtonGroup"
/>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<>
{selectedView === VIEW.HITS_TABLE && (
<ResultsTable hits={props.response?.hits?.hits || []} />
)}
{selectedView === VIEW.RAW_JSON && (
<ResultsJSON response={props.response} />
)}
</>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
}
34 changes: 34 additions & 0 deletions public/general_components/results/results_json.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { EuiCodeEditor } from '@elastic/eui';
import { customStringify, SearchResponse } from '../../../common';

interface ResultsJSONProps {
response: SearchResponse;
}

/**
* Small component to render the raw search response.
*/
export function ResultsJSON(props: ResultsJSONProps) {
return (
<EuiCodeEditor
mode="json"
theme="textmate"
width="100%"
height="100%"
value={customStringify(props.response)}
readOnly={true}
setOptions={{
fontSize: '12px',
autoScrollEditorIntoView: true,
wrap: true,
}}
tabSize={2}
/>
);
}
111 changes: 111 additions & 0 deletions public/general_components/results/results_table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState } from 'react';
import {
EuiText,
EuiButtonIcon,
RIGHT_ALIGNMENT,
EuiInMemoryTable,
EuiCodeEditor,
EuiPanel,
} from '@elastic/eui';
import { customStringify, SearchHit } from '../../../common';

interface ResultsTableProps {
hits: SearchHit[];
}

/**
* Small component to display a list of search results with pagination.
* Can expand each entry to view the full _source response
*/
export function ResultsTable(props: ResultsTableProps) {
const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<{
[itemId: string]: any;
}>({});

const toggleDetails = (hit: SearchHit) => {
const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap };
if (itemIdToExpandedRowMapValues[hit._id]) {
delete itemIdToExpandedRowMapValues[hit._id];
} else {
itemIdToExpandedRowMapValues[hit._id] = (
<EuiPanel
style={{ height: '20vh' }}
hasShadow={false}
hasBorder={false}
paddingSize="none"
>
<EuiCodeEditor
mode="json"
theme="textmate"
width="100%"
height="100%"
value={customStringify(hit._source)}
readOnly={true}
setOptions={{
fontSize: '12px',
autoScrollEditorIntoView: true,
wrap: true,
}}
tabSize={2}
/>
</EuiPanel>
);
}
setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues);
};

return (
<EuiInMemoryTable
itemId="_id"
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
items={props.hits}
isExpandable={true}
compressed={true}
pagination={true}
tableLayout="auto"
columns={[
{
field: '_id',
name: '',
sortable: false,
render: (_, item: SearchHit) => {
return (
<EuiText
size="s"
color="subdued"
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
width: '20vw',
}}
>
{customStringify(item._source)}
</EuiText>
);
},
},
{
align: RIGHT_ALIGNMENT,
width: '40px',
isExpander: true,
render: (item: SearchHit) => (
<EuiButtonIcon
onClick={() => toggleDetails(item)}
aria-label={
itemIdToExpandedRowMap[item._id] ? 'Collapse' : 'Expand'
}
iconType={
itemIdToExpandedRowMap[item._id] ? 'arrowUp' : 'arrowDown'
}
/>
),
},
]}
/>
);
}
3 changes: 0 additions & 3 deletions public/pages/workflow_detail/resizable_workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
// Inspector panel state vars. Actions taken in the form can update the Inspector panel,
// hence we keep top-level vars here to pass to both form and inspector components.
const [ingestResponse, setIngestResponse] = useState<string>('');
const [queryResponse, setQueryResponse] = useState<string>('');
const [selectedInspectorTabId, setSelectedInspectorTabId] = useState<
INSPECTOR_TAB_ID
>(INSPECTOR_TAB_ID.INGEST);
Expand Down Expand Up @@ -207,8 +206,6 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
<Tools
workflow={props.workflow}
ingestResponse={ingestResponse}
queryResponse={queryResponse}
setQueryResponse={setQueryResponse}
selectedTabId={selectedInspectorTabId}
setSelectedTabId={setSelectedInspectorTabId}
selectedStep={props.selectedStep}
Expand Down
45 changes: 13 additions & 32 deletions public/pages/workflow_detail/tools/query/query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
customStringify,
FETCH_ALL_QUERY,
QueryParam,
SearchHit,
SearchResponse,
WorkflowFormValues,
} from '../../../../../common';
import { searchIndex, useAppDispatch } from '../../../../store';
Expand All @@ -32,11 +32,9 @@ import {
getPlaceholdersFromQuery,
injectParameters,
} from '../../../../utils';
import { QueryParamsList } from '../../../../general_components';
import { QueryParamsList, Results } from '../../../../general_components';

interface QueryProps {
queryResponse: string;
setQueryResponse: (queryResponse: string) => void;
hasSearchPipeline: boolean;
hasIngestResources: boolean;
selectedStep: CONFIG_STEP;
Expand Down Expand Up @@ -65,6 +63,11 @@ export function Query(props: QueryProps) {
// use custom query state
const [useCustomQuery, setUseCustomQuery] = useState<boolean>(false);

// query response state
const [queryResponse, setQueryResponse] = useState<
SearchResponse | undefined
>(undefined);

// Standalone / sandboxed search request state. Users can test things out
// without updating the base form / persisted value. We default to different values
// based on the context (ingest or search), and update based on changes to the context
Expand Down Expand Up @@ -116,7 +119,7 @@ export function Query(props: QueryProps) {
}))
);
}
props.setQueryResponse('');
setQueryResponse(undefined);
}, [tempRequest]);

// empty states
Expand Down Expand Up @@ -182,17 +185,11 @@ export function Query(props: QueryProps) {
})
)
.unwrap()
.then(async (resp) => {
props.setQueryResponse(
customStringify(
resp?.hits?.hits?.map(
(hit: SearchHit) => hit._source
)
)
);
.then(async (resp: SearchResponse) => {
setQueryResponse(resp);
})
.catch((error: any) => {
props.setQueryResponse('');
setQueryResponse(undefined);
console.error('Error running query: ', error);
});
}}
Expand Down Expand Up @@ -283,7 +280,7 @@ export function Query(props: QueryProps) {
<EuiText size="m">Results</EuiText>
</EuiFlexItem>
<EuiFlexItem>
{isEmpty(props.queryResponse) ? (
{queryResponse === undefined || isEmpty(queryResponse) ? (
<EuiEmptyPrompt
title={<h2>No results</h2>}
titleSize="s"
Expand All @@ -294,23 +291,7 @@ export function Query(props: QueryProps) {
}
/>
) : (
// Known issue with the editor where resizing the resizablecontainer does not
// trigger vertical scroll updates. Updating the window, or reloading the component
// by switching tabs etc. will refresh it correctly
<EuiCodeEditor
mode="json"
theme="textmate"
width="100%"
height="100%"
value={props.queryResponse}
readOnly={true}
setOptions={{
fontSize: '12px',
autoScrollEditorIntoView: true,
wrap: true,
}}
tabSize={2}
/>
<Results response={queryResponse} />
)}
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Loading

0 comments on commit 72c32e6

Please sign in to comment.