Skip to content

Commit

Permalink
Saved queries new UI (opensearch-project#8469)
Browse files Browse the repository at this point in the history
* loading query functional

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* search bar working; save working

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* implemented pagination

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* Changeset file for PR opensearch-project#8469 created/updated

* implemented tab for template queries; updated UI elements for new mocks

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* added i18n; minor refactors

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* refactored css

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* addressed comments

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* fixed i18n error

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* show template queries tab only when templates present

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* addressed PR comments

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* fixed unit tests

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* introduced a new gate for the changes

Signed-off-by: Amardeepsingh Siglani <[email protected]>

---------

Signed-off-by: Amardeepsingh Siglani <[email protected]>
Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
  • Loading branch information
amsiglan and opensearch-changeset-bot[bot] committed Oct 25, 2024
1 parent ac7bf02 commit 2773848
Show file tree
Hide file tree
Showing 25 changed files with 1,244 additions and 284 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/8469.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- Enhances the saved query UX ([#8469](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8469))
5 changes: 4 additions & 1 deletion config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,7 @@
# The default config is [], and no one will be dashboard admin.
# If the user config is set to wildcard ["*"], anyone will be dashboard admin.
# opensearchDashboards.dashboardAdmin.groups: ["dashboard_admin"]
# opensearchDashboards.dashboardAdmin.users: ["dashboard_admin"]
# opensearchDashboards.dashboardAdmin.users: ["dashboard_admin"]

# Set the value to true to enable the new UI for savedQueries in Discover
# data.savedQueriesNewUI.enabled: true
3 changes: 3 additions & 0 deletions src/plugins/data/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: false }),
}),
}),
savedQueriesNewUI: schema.object({
enabled: schema.boolean({ defaultValue: false }),
}),
});

export type ConfigSchema = TypeOf<typeof configSchema>;
10 changes: 9 additions & 1 deletion src/plugins/data/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
} from './index_patterns';
import {
setApplication,
setUseNewSavedQueriesUI,
setFieldFormats,
setIndexPatterns,
setNotifications,
Expand Down Expand Up @@ -93,7 +94,7 @@ import { DEFAULT_DATA_SOURCE_TYPE } from './data_sources/constants';
import { getSuggestions as getSQLSuggestions } from './antlr/opensearch_sql/code_completion';
import { getSuggestions as getDQLSuggestions } from './antlr/dql/code_completion';
import { getSuggestions as getPPLSuggestions } from './antlr/opensearch_ppl/code_completion';
import { createStorage, DataStorage } from '../common';
import { createStorage, DataStorage, UI_SETTINGS } from '../common';

declare module '../../ui_actions/public' {
export interface ActionContextMapping {
Expand All @@ -118,6 +119,7 @@ export class DataPublicPlugin
private readonly queryService: QueryService;
private readonly storage: DataStorage;
private readonly sessionStorage: DataStorage;
private readonly config: ConfigSchema;

constructor(initializerContext: PluginInitializerContext<ConfigSchema>) {
this.searchService = new SearchService(initializerContext);
Expand All @@ -130,6 +132,7 @@ export class DataPublicPlugin
engine: window.sessionStorage,
prefix: 'opensearchDashboards.',
});
this.config = initializerContext.config.get();
}

public setup(
Expand Down Expand Up @@ -178,6 +181,11 @@ export class DataPublicPlugin
autoComplete.addQuerySuggestionProvider('kuery', getDQLSuggestions);
autoComplete.addQuerySuggestionProvider('PPL', getPPLSuggestions);

const useNewSavedQueriesUI =
core.uiSettings.get(UI_SETTINGS.QUERY_ENHANCEMENTS_ENABLED) &&
this.config.savedQueriesNewUI.enabled;
setUseNewSavedQueriesUI(useNewSavedQueriesUI);

return {
// TODO: MQL
autocomplete: this.autocomplete.setup(core),
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/data/public/query/query_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ export class QueryService {
queryString: this.queryStringManager,
savedQueries: createSavedQueryService(
savedObjectsClient,
this.queryStringManager,
application
{ application, uiSettings },
this.queryStringManager
),
state$: this.state$,
timefilter: this.timefilter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import { createSavedQueryService } from './saved_query_service';
import { FilterStateStore } from '../../../common';
import { SavedQueryAttributes } from './types';
import { applicationServiceMock, uiSettingsServiceMock } from '../../../../../core/public/mocks';

const savedQueryAttributes: SavedQueryAttributes = {
title: 'foo',
Expand Down Expand Up @@ -89,7 +90,11 @@ const {
getSavedQueryCount,
} = createSavedQueryService(
// @ts-ignore
mockSavedObjectsClient
mockSavedObjectsClient,
{
application: applicationServiceMock.create(),
uiSettings: uiSettingsServiceMock.createStartContract(),
}
);

describe('saved query service', () => {
Expand Down
50 changes: 28 additions & 22 deletions src/plugins/data/public/query/saved_query/saved_query_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,35 +33,36 @@ import { SavedObjectsClientContract, SavedObjectAttributes, CoreStart } from 'sr
import { first } from 'rxjs/operators';
import { SavedQueryAttributes, SavedQuery, SavedQueryService } from './types';
import { QueryStringContract } from '../query_string';
import { getUseNewSavedQueriesUI } from '../../services';

type SerializedSavedQueryAttributes = SavedObjectAttributes &
SavedQueryAttributes & {
query: {
query: string;
language: string;
};
};
type SerializedSavedQueryAttributes = SavedObjectAttributes & SavedQueryAttributes;

export const createSavedQueryService = (
savedObjectsClient: SavedObjectsClientContract,
queryStringManager?: QueryStringContract,
application?: CoreStart['application']
coreStartServices: { application: CoreStart['application']; uiSettings: CoreStart['uiSettings'] },
queryStringManager?: QueryStringContract
): SavedQueryService => {
const { application } = coreStartServices;

const saveQuery = async (attributes: SavedQueryAttributes, { overwrite = false } = {}) => {
if (!attributes.title.length) {
// title is required extra check against circumventing the front end
throw new Error('Cannot create saved query without a title');
}

const query = {
const query: SerializedSavedQueryAttributes['query'] = {
query:
typeof attributes.query.query === 'string'
? attributes.query.query
: JSON.stringify(attributes.query.query),
language: attributes.query.language,
};

const queryObject: SerializedSavedQueryAttributes = {
if (getUseNewSavedQueriesUI() && attributes.query.dataset) {
query.dataset = attributes.query.dataset;
}

const queryObject: SavedQueryAttributes = {
title: attributes.title.trim(), // trim whitespace before save as an extra precaution against circumventing the front end
description: attributes.description,
query,
Expand All @@ -75,6 +76,10 @@ export const createSavedQueryService = (
queryObject.timefilter = attributes.timefilter;
}

if (getUseNewSavedQueriesUI() && attributes.isTemplate) {
queryObject.isTemplate = true;
}

let rawQueryResponse;
if (!overwrite) {
rawQueryResponse = await savedObjectsClient.create('query', queryObject, {
Expand Down Expand Up @@ -126,8 +131,7 @@ export const createSavedQueryService = (
parseSavedQueryObject(savedObject)
);

const currentAppId =
(await application?.currentAppId$?.pipe(first()).toPromise()) ?? Promise.resolve(undefined);
const currentAppId = (await application?.currentAppId$?.pipe(first()).toPromise()) ?? undefined;
const languageService = queryStringManager?.getLanguageService();

// Filtering saved queries based on language supported by cirrent application
Expand Down Expand Up @@ -159,11 +163,8 @@ export const createSavedQueryService = (
return await savedObjectsClient.delete('query', id);
};

const parseSavedQueryObject = (savedQuery: {
id: string;
attributes: SerializedSavedQueryAttributes;
}) => {
const queryString = savedQuery.attributes.query.query;
const parseSavedQueryObject = (savedQuery: SavedQuery) => {
const queryString = savedQuery.attributes.query.query as string;
let parsedQuery;
try {
parsedQuery = JSON.parse(queryString);
Expand All @@ -172,7 +173,7 @@ export const createSavedQueryService = (
parsedQuery = queryString;
}

const savedQueryItems: SavedQueryAttributes = {
const savedQueryItem: SavedQueryAttributes = {
title: savedQuery.attributes.title || '',
description: savedQuery.attributes.description || '',
query: {
Expand All @@ -181,15 +182,20 @@ export const createSavedQueryService = (
},
};

if (getUseNewSavedQueriesUI()) {
savedQueryItem.query.dataset = savedQuery.attributes.query.dataset;
savedQueryItem.isTemplate = !!savedQuery.attributes.isTemplate;
}

if (savedQuery.attributes.filters) {
savedQueryItems.filters = savedQuery.attributes.filters;
savedQueryItem.filters = savedQuery.attributes.filters;
}
if (savedQuery.attributes.timefilter) {
savedQueryItems.timefilter = savedQuery.attributes.timefilter;
savedQueryItem.timefilter = savedQuery.attributes.timefilter;
}
return {
id: savedQuery.id,
attributes: savedQueryItems,
attributes: savedQueryItem,
};
};

Expand Down
3 changes: 3 additions & 0 deletions src/plugins/data/public/query/saved_query/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export interface SavedQuery {
export interface SavedQueryAttributes {
title: string;
description: string;
// If isTemplate is true, then saved query cannot be updated/deleted from the UI and
// will show up under Templates tab for saved queries
isTemplate?: boolean;
query: Query;
filters?: Filter[];
timefilter?: SavedQueryTimeFilter;
Expand Down
10 changes: 10 additions & 0 deletions src/plugins/data/public/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,13 @@ export const [getSearchService, setSearchService] = createGetterSetter<
export const [getUiService, setUiService] = createGetterSetter<DataPublicPluginStart['ui']>('Ui');

export const [getApplication, setApplication] = createGetterSetter<ApplicationStart>('Application');

let useNewSavedQueriesUI = false;

export function getUseNewSavedQueriesUI() {
return useNewSavedQueriesUI;
}

export const setUseNewSavedQueriesUI = (value: boolean) => {
useNewSavedQueriesUI = value;
};
1 change: 1 addition & 0 deletions src/plugins/data/public/ui/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
@import "./dataset_selector/index";
@import "./query_editor/index";
@import "./shard_failure_modal/shard_failure_modal";
@import "./saved_query_flyouts/saved_query_flyouts";
24 changes: 16 additions & 8 deletions src/plugins/data/public/ui/filter_bar/filter_options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ import {
unpinFilter,
UI_SETTINGS,
IIndexPattern,
isQueryStringFilter,
} from '../../../common';
import { FilterEditor } from './filter_editor';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { SavedQueryManagementComponent } from '../saved_query_management';
import { SavedQuery, SavedQueryService } from '../../query';
import { SavedQueryMeta } from '../saved_query_form';
import { getUseNewSavedQueriesUI } from '../../services';

interface Props {
intl: InjectedIntl;
Expand All @@ -69,14 +70,15 @@ interface Props {
savedQueryService: SavedQueryService;
// Show when user has privileges to save
showSaveQuery?: boolean;
onSave: () => void;
onSaveAsNew: () => void;
onInitiateSave: () => void;
onInitiateSaveAsNew: () => void;
onLoad: (savedQuery: SavedQuery) => void;
onClearSavedQuery: () => void;
onFiltersUpdated?: (filters: Filter[]) => void;
loadedSavedQuery?: SavedQuery;
useSaveQueryMenu: boolean;
isQueryEditorControl: boolean;
saveQuery: (savedQueryMeta: SavedQueryMeta, saveAsNew?: boolean) => Promise<void>;
}
const maxFilterWidth = 600;

Expand Down Expand Up @@ -285,8 +287,8 @@ const FilterOptionsUI = (props: Props) => {
];

const handleSave = () => {
if (props.onSave) {
props.onSave();
if (props.onInitiateSave) {
props.onInitiateSave();
}
setIsPopoverOpen(false);
};
Expand All @@ -297,15 +299,17 @@ const FilterOptionsUI = (props: Props) => {
<SavedQueryManagementComponent
showSaveQuery={props.showSaveQuery}
loadedSavedQuery={props.loadedSavedQuery}
onSave={handleSave}
onSaveAsNew={props.onSaveAsNew!}
onInitiateSave={handleSave}
onInitiateSaveAsNew={props.onInitiateSaveAsNew!}
onLoad={props.onLoad!}
savedQueryService={props.savedQueryService!}
onClearSavedQuery={props.onClearSavedQuery!}
closeMenuPopover={() => {
setIsPopoverOpen(false);
}}
key={'savedQueryManagement'}
useNewSavedQueryUI={getUseNewSavedQueriesUI()}
saveQuery={props.saveQuery}
/>,
]}
data-test-subj="save-query-panel"
Expand Down Expand Up @@ -364,6 +368,10 @@ const FilterOptionsUI = (props: Props) => {
defaultMessage: 'See saved queries',
});

const iconForQueryEditorControlPopoverBtn = getUseNewSavedQueriesUI()
? 'boxesHorizontal'
: 'folderOpen';

const savedQueryPopoverButton = (
<EuiSmallButtonEmpty
onClick={togglePopover}
Expand All @@ -373,7 +381,7 @@ const FilterOptionsUI = (props: Props) => {
title={label}
>
<EuiIcon
type={props.isQueryEditorControl ? 'folderOpen' : 'save'}
type={props.isQueryEditorControl ? iconForQueryEditorControlPopoverBtn : 'save'}
className="euiQuickSelectPopover__buttonText"
/>
</EuiSmallButtonEmpty>
Expand Down
Loading

0 comments on commit 2773848

Please sign in to comment.