diff --git a/packages/core/src/environment.ts b/packages/core/src/environment.ts index 731fc501..5ac5f3d5 100644 --- a/packages/core/src/environment.ts +++ b/packages/core/src/environment.ts @@ -1,5 +1,5 @@ import { core, devspace } from '@sap/bas-sdk'; -import type { GuidedAnswersQueryFilterOptions, IDE } from '@sap/guided-answers-extension-types'; +import type { FilterStack, IDE } from '@sap/guided-answers-extension-types'; import environmentJson from './environment.json'; const devSpaceComponentsMap: { [devspace: string]: string[] } = environmentJson.devSpaceComponentsMap; @@ -42,8 +42,8 @@ export async function getDevSpace(): Promise { * @param ide - runtime IDE, VSCODE or SBAS * @returns - filters for given ide */ -export async function getFiltersForIde(ide: IDE): Promise { - const filterOptions: GuidedAnswersQueryFilterOptions = {}; +export async function getFiltersForIde(ide: IDE): Promise { + const filterOptions: FilterStack = {}; let components: Set = new Set(); let basDevSpace; diff --git a/packages/core/src/guided-answers-api.ts b/packages/core/src/guided-answers-api.ts index 45b29a8f..e44dd151 100644 --- a/packages/core/src/guided-answers-api.ts +++ b/packages/core/src/guided-answers-api.ts @@ -11,7 +11,7 @@ import type { GuidedAnswerNode, GuidedAnswerNodeId, GuidedAnswersFeedback, - GuidedAnswersQueryFilterOptions, + FilterStack, GuidedAnswersQueryOptions, GuidedAnswersQueryPagingOptions, GuidedAnswerTree, @@ -96,13 +96,13 @@ function convertImageSrc(body: string, host: string): string { * Convert query filter options to URL get parameter string. * * @param filters - optional filters - * @param filters.component - optional component filter - * @param filters.product - optional product filter + * @param filters.component - component filter + * @param filters.product - product filter * @param paging - optional paging, if not provided set to high response size with 0 offset * @returns - URL get parameters as string */ function convertQueryOptionsToGetParams( - filters: GuidedAnswersQueryFilterOptions = {}, + filters: FilterStack = {}, paging: GuidedAnswersQueryPagingOptions = { responseSize: DEFAULT_MAX_RESULTS, offset: 0 } ): string { const parameters = [ @@ -110,7 +110,7 @@ function convertQueryOptionsToGetParams( ...Object.keys(filters).map( (filterName) => `${filterName}=${filters[filterName as keyof typeof filters] - ?.map((filterValue) => encodeURIComponent(`"${filterValue}"`)) + .map((filterValue) => encodeURIComponent(`"${filterValue}"`)) .join(',')}` ), // Paging parameters diff --git a/packages/ide-extension/src/custom-filters/customFilters.ts b/packages/ide-extension/src/custom-filters/customFilters.ts new file mode 100644 index 00000000..d2b5f36b --- /dev/null +++ b/packages/ide-extension/src/custom-filters/customFilters.ts @@ -0,0 +1,34 @@ +import type { Memento } from 'vscode'; +import type { FilterStack } from '@sap/guided-answers-extension-types'; +import { logError } from '../logger/logger'; + +let globalStateApi: Memento; + +/** + * Initialize the custom filters functionality by passing VSCode global state. + * + * @param globalState - VSCodes global state for extension + */ +export function initCustomFilters(globalState: Memento) { + globalStateApi = globalState; +} + +/** + * Return the list of stored custom filters. + * + * @returns - list of custom filters + */ +export function getAllCustomFilters(): FilterStack[] { + return globalStateApi?.get('customFilters') ?? []; +} + +/** + * Update the stored custom filters. + * + * @param customFilters - array of custom filters + */ +export function updateCustomFilters(customFilters: FilterStack[]) { + globalStateApi + .update('customFilters', customFilters) + .then(undefined, (error) => logError(`Error updating customFilters.`, error)); +} diff --git a/packages/ide-extension/src/custom-filters/index.ts b/packages/ide-extension/src/custom-filters/index.ts new file mode 100644 index 00000000..0b481482 --- /dev/null +++ b/packages/ide-extension/src/custom-filters/index.ts @@ -0,0 +1 @@ +export { getAllCustomFilters, initCustomFilters, updateCustomFilters } from './customFilters'; diff --git a/packages/ide-extension/src/extension.ts b/packages/ide-extension/src/extension.ts index 51875828..7845c043 100644 --- a/packages/ide-extension/src/extension.ts +++ b/packages/ide-extension/src/extension.ts @@ -8,6 +8,7 @@ import { GuidedAnswersUriHandler } from './links'; import type { StartOptions } from './types'; import { initBookmarks } from './bookmarks'; import { initLastVisited } from './last-visited'; +import { initCustomFilters } from './custom-filters'; /** * Activate function is called by VSCode when the extension gets active. @@ -23,6 +24,7 @@ export function activate(context: ExtensionContext): void { try { initBookmarks(context.globalState); initLastVisited(context.globalState); + initCustomFilters(context.globalState); } catch (error) { logError(`Error during initialization of functionality that relies on VSCode's global state storage.`, error); } diff --git a/packages/ide-extension/src/panel/guidedAnswersPanel.ts b/packages/ide-extension/src/panel/guidedAnswersPanel.ts index d7462c30..13412d15 100644 --- a/packages/ide-extension/src/panel/guidedAnswersPanel.ts +++ b/packages/ide-extension/src/panel/guidedAnswersPanel.ts @@ -18,6 +18,7 @@ import { SYNCHRONIZE_BOOKMARK, UPDATE_BOOKMARKS, UPDATE_LAST_VISITED_GUIDES, + UPDATE_CUSTOM_FILTERS, updateGuidedAnswerTrees, updateActiveNode, updateNetworkStatus, @@ -34,7 +35,8 @@ import { goToAllAnswers, updateBookmark, getLastVisitedGuides, - setQuickFilters + getAutoFilters, + getCustomFilters } from '@sap/guided-answers-extension-types'; import { getFiltersForIde, getGuidedAnswerApi } from '@sap/guided-answers-extension-core'; import { getHtml } from './html'; @@ -45,6 +47,7 @@ import { setCommonProperties, trackAction, trackEvent } from '../telemetry'; import { extractLinkInfo, generateExtensionLink, generateWebLink } from '../links/link-info'; import { getAllBookmarks, updateBookmarks } from '../bookmarks'; import { updateLastVisitedGuides, getAllLastVisitedGuides } from '../last-visited'; +import { getAllCustomFilters, updateCustomFilters } from '../custom-filters'; /** * Class that represents the Guided Answers panel, which hosts the webview UI. @@ -164,13 +167,14 @@ export class GuidedAnswersPanel { if (this.startOptions) { await this.processStartOptions(this.startOptions); } - await this.loadQuickFilters(this.ide); + await this.loadAutoFilters(this.ide); this.postActionToWebview( getBetaFeatures(workspace.getConfiguration('sap.ux.guidedAnswer').get('betaFeatures') ?? false) ); this.postActionToWebview(updateNetworkStatus('OK')); this.postActionToWebview(getBookmarks(getAllBookmarks())); this.postActionToWebview(getLastVisitedGuides(getAllLastVisitedGuides())); + this.postActionToWebview(getCustomFilters(getAllCustomFilters())); } /** @@ -214,12 +218,12 @@ export class GuidedAnswersPanel { * * @param ide - environment like VSCODE or BAS */ - private async loadQuickFilters(ide: IDE): Promise { + private async loadAutoFilters(ide: IDE): Promise { try { const filters = await getFiltersForIde(ide); logger.logInfo(`Filters for environment '${ide}':`, filters); if (Object.keys(filters).length > 0) { - this.postActionToWebview(setQuickFilters([filters])); + this.postActionToWebview(getAutoFilters([filters])); } } catch (error: any) { logger.logError(`Error while retrieving context information, error was:`, error); @@ -379,6 +383,11 @@ export class GuidedAnswersPanel { ); break; } + case UPDATE_CUSTOM_FILTERS: { + updateCustomFilters(action.payload); + this.postActionToWebview(getCustomFilters(getAllCustomFilters())); + break; + } default: { // Nothing to do if the action is not handled } diff --git a/packages/ide-extension/test/panel/guidedAnswersPanel.test.ts b/packages/ide-extension/test/panel/guidedAnswersPanel.test.ts index 59bfbf26..15170961 100644 --- a/packages/ide-extension/test/panel/guidedAnswersPanel.test.ts +++ b/packages/ide-extension/test/panel/guidedAnswersPanel.test.ts @@ -18,7 +18,6 @@ import { RESTORE_STATE, SEND_TELEMETRY, GET_BOOKMARKS, - AppState, SYNCHRONIZE_BOOKMARK, UPDATE_BOOKMARKS, GET_LAST_VISITED_GUIDES, @@ -34,7 +33,8 @@ import type { GuidedAnswerTree, GuidedAnswerTreeId, GuidedAnswerTreeSearchResult, - LastVisitedGuide + LastVisitedGuide, + AppState } from '@sap/guided-answers-extension-types'; import { GuidedAnswersPanel, GuidedAnswersSerializer } from '../../src/panel/guidedAnswersPanel'; import * as logger from '../../src/logger/logger'; @@ -58,7 +58,7 @@ const getWebViewPanelMock = (onDidReceiveMessage: (callback: WebviewMessageCallb onDidChangeViewState: jest.fn(), onDidDispose: jest.fn(), reveal: jest.fn() - } as unknown as WebviewPanel); + }) as unknown as WebviewPanel; const getApiMock = (firstNodeId?: number): GuidedAnswerAPI => ({ @@ -78,7 +78,7 @@ const getApiMock = (firstNodeId?: number): GuidedAnswerAPI => getTrees: () => Promise.resolve([{ TREE_ID: 1 }, { TREE_ID: 2 }, { TREE_ID: 3 }]), sendFeedbackOutcome: jest.fn(), sendFeedbackComment: jest.fn() - } as unknown as GuidedAnswerAPI); + }) as unknown as GuidedAnswerAPI; const delay = async (ms: number) => { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -132,7 +132,7 @@ describe('GuidedAnswersPanel', () => { expect(loggerMock.info).toBeCalledWith('Webview is ready to receive actions'); }); - test('GuidedAnswersPanel for SBAS, should set quick filters', async () => { + test('GuidedAnswersPanel for SBAS, should set auto filters', async () => { // Mock setup let onDidReceiveMessageMock: WebviewMessageCallback = () => {}; const webViewPanelMock = getWebViewPanelMock((callback: WebviewMessageCallback) => { @@ -152,9 +152,9 @@ describe('GuidedAnswersPanel', () => { expect(loggerMock.info).toBeCalledWith('Webview is ready to receive actions'); expect(getFiltersForIdeSpy).toBeCalledWith('SBAS'); const searchCall = (webViewPanelMock.webview.postMessage as jest.Mock).mock.calls.find((c) => - c.find((p: { type: string }) => p.type === 'SET_QUICK_FILTERS') + c.find((p: { type: string }) => p.type === 'SET_AUTO_FILTERS') )[0]; - expect(searchCall).toEqual({ type: 'SET_QUICK_FILTERS', payload: [{ component: ['AB-CD', 'EFG-H'] }] }); + expect(searchCall).toEqual({ type: 'SET_AUTO_FILTERS', payload: [{ component: ['AB-CD', 'EFG-H'] }] }); }); test('GuidedAnswersPanel for SBAS with error in getFiltersForIde(), should log error', async () => { diff --git a/packages/types/src/actions.ts b/packages/types/src/actions.ts index 96123c77..33d7a984 100644 --- a/packages/types/src/actions.ts +++ b/packages/types/src/actions.ts @@ -46,8 +46,10 @@ import type { UpdateLastVisitedGuides, UpdateBookmarksPayload, GoToHomePage, - SetQuickFilters, - GuidedAnswersQueryFilterOptions + GetAutoFilters, + GetCustomFilters, + UpdateCustomFilters, + FilterStack } from './types'; import { EXECUTE_COMMAND, @@ -84,7 +86,9 @@ import { GET_LAST_VISITED_GUIDES, UPDATE_LAST_VISITED_GUIDES, GO_TO_HOME_PAGE, - SET_QUICK_FILTERS + GET_AUTO_FILTERS, + GET_CUSTOM_FILTERS, + UPDATE_CUSTOM_FILTERS } from './types'; export const updateGuidedAnswerTrees = (payload: UpdateGuidedAnswerTrees['payload']): UpdateGuidedAnswerTrees => ({ @@ -243,7 +247,17 @@ export const updateLastVisitedGuide = (payload: LastVisitedGuide[]): UpdateLastV payload }); -export const setQuickFilters = (payload: GuidedAnswersQueryFilterOptions[]): SetQuickFilters => ({ - type: SET_QUICK_FILTERS, +export const getAutoFilters = (payload: FilterStack[]): GetAutoFilters => ({ + type: GET_AUTO_FILTERS, + payload +}); + +export const getCustomFilters = (payload: FilterStack[]): GetCustomFilters => ({ + type: GET_CUSTOM_FILTERS, + payload +}); + +export const updateCustomFilters = (payload: FilterStack[]): UpdateCustomFilters => ({ + type: UPDATE_CUSTOM_FILTERS, payload }); diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 46d76163..66d8f852 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -14,13 +14,13 @@ export interface GuidedAnswerTree { export interface GuidedAnswersQueryOptions { query?: string; - filters?: GuidedAnswersQueryFilterOptions; + filters?: FilterStack; paging?: GuidedAnswersQueryPagingOptions; } -export interface GuidedAnswersQueryFilterOptions { - component?: string[]; - product?: string[]; +export interface FilterStack { + component: string[]; + product: string[]; } export interface GuidedAnswersQueryPagingOptions { @@ -219,7 +219,9 @@ export type GuidedAnswerActions = | UpdateGuidedAnswerTrees | UpdateNetworkStatus | WebviewReady - | SetQuickFilters; + | GetAutoFilters + | GetCustomFilters + | UpdateCustomFilters; export type NetworkStatus = 'OK' | 'LOADING' | 'ERROR'; @@ -232,15 +234,15 @@ export interface AppState { activeNodeSharing: ShareNodeLinks | null; betaFeatures: boolean; guideFeedback: null | boolean; - selectedProductFilters: string[]; - selectedComponentFilters: string[]; + selectedFilters: FilterStack; pageSize: number; feedbackStatus: boolean; feedbackResponse: boolean; bookmarks: Bookmarks; activeScreen: 'HOME' | 'SEARCH' | 'NODE'; lastVisitedGuides: LastVisitedGuide[]; - quickFilters: GuidedAnswersQueryFilterOptions[]; + autoFilters: FilterStack[]; + customFilters: FilterStack[]; } export const UPDATE_GUIDED_ANSWER_TREES = 'UPDATE_GUIDED_ANSWER_TREES'; @@ -447,8 +449,20 @@ export interface SynchronizeBookmark { payload: Bookmark; } -export const SET_QUICK_FILTERS = 'SET_QUICK_FILTERS'; -export interface SetQuickFilters { - type: typeof SET_QUICK_FILTERS; - payload: GuidedAnswersQueryFilterOptions[]; +export const GET_AUTO_FILTERS = 'GET_AUTO_FILTERS'; +export interface GetAutoFilters { + type: typeof GET_AUTO_FILTERS; + payload: FilterStack[]; +} + +export const GET_CUSTOM_FILTERS = 'GET_CUSTOM_FILTERS'; +export interface GetCustomFilters { + type: typeof GET_CUSTOM_FILTERS; + payload: FilterStack[]; +} + +export const UPDATE_CUSTOM_FILTERS = 'UPDATE_CUSTOM_FILTERS'; +export interface UpdateCustomFilters { + type: typeof UPDATE_CUSTOM_FILTERS; + payload: FilterStack[]; } diff --git a/packages/webapp/src/webview/i18n/i18n.json b/packages/webapp/src/webview/i18n/i18n.json index e48d4b61..1298d852 100644 --- a/packages/webapp/src/webview/i18n/i18n.json +++ b/packages/webapp/src/webview/i18n/i18n.json @@ -39,5 +39,7 @@ "COPIED_TO_CLIPBOARD_DESC": "Copy this link to share via email or messages. When pasted into the search input field of the Guided Answers extension, it will navigate you straight to this node.", "VIEW_ON_WEBSITE": "View on the GA website", "COPY_WITH_INSTRUCTIONS": "Copy link with instructions", - "COPY_WITH_INSTRUCTIONS_TEXT": "To resolve your reported issue, open Guided Answers extension by SAP, and follow the steps mentioned in the guide.\n If your IDE is VSCode, please click on the following link: {{-extensionLink}} .\n If your IDE is SAP Business Application Studio, then launch Guided Answers via the command \"SAP: Open Guided Answers\" and paste the following guide shortlink into the search input field: {{-extensionLink}} ." + "COPY_WITH_INSTRUCTIONS_TEXT": "To resolve your reported issue, open Guided Answers extension by SAP, and follow the steps mentioned in the guide.\n If your IDE is VSCode, please click on the following link: {{-extensionLink}} .\n If your IDE is SAP Business Application Studio, then launch Guided Answers via the command \"SAP: Open Guided Answers\" and paste the following guide shortlink into the search input field: {{-extensionLink}} .", + "CLEAR": "Clear", + "SAVE": "Save" } diff --git a/packages/webapp/src/webview/state/actions.ts b/packages/webapp/src/webview/state/actions.ts index 0c21cae6..be01f44f 100644 --- a/packages/webapp/src/webview/state/actions.ts +++ b/packages/webapp/src/webview/state/actions.ts @@ -30,5 +30,6 @@ export { getLastVisitedGuides, updateBookmark, synchronizeBookmark, - updateLastVisitedGuide + updateLastVisitedGuide, + updateCustomFilters } from '@sap/guided-answers-extension-types'; diff --git a/packages/webapp/src/webview/state/reducers.ts b/packages/webapp/src/webview/state/reducers.ts index 5b7d8375..e862f445 100644 --- a/packages/webapp/src/webview/state/reducers.ts +++ b/packages/webapp/src/webview/state/reducers.ts @@ -19,7 +19,8 @@ import type { FeedbackStatus, GuidedAnswerNode, GetLastVisitedGuides, - SetQuickFilters + GetAutoFilters, + GetCustomFilters } from '@sap/guided-answers-extension-types'; import i18next from 'i18next'; import type { Reducer } from 'redux'; @@ -44,15 +45,18 @@ export function getInitialState(): AppState { activeGuidedAnswerNode: [], betaFeatures: false, guideFeedback: null, - selectedProductFilters: [], - selectedComponentFilters: [], + selectedFilters: { + product: [], + component: [] + }, pageSize: 20, feedbackStatus: false, feedbackResponse: false, bookmarks: {}, activeScreen: 'HOME', lastVisitedGuides: [], - quickFilters: [] + autoFilters: [], + customFilters: [] }; } @@ -99,7 +103,8 @@ const reducers: Partial = { GET_BOOKMARKS: getBookmarksReducer, UPDATE_BOOKMARKS: updateBookmarksReducer, GET_LAST_VISITED_GUIDES: getLastVisitedGuidesReducer, - SET_QUICK_FILTERS: setQuickFiltersReducer + GET_AUTO_FILTERS: getAutoFiltersReducer, + GET_CUSTOM_FILTERS: getCustomFiltersReducer }; /** @@ -421,8 +426,9 @@ function resetFiltersReducer(newState: AppState): AppState { * @returns new state with changes */ function searchTreeReducer(newState: AppState, action: SearchTree): AppState { - newState.selectedComponentFilters = action.payload.filters?.component ?? []; - newState.selectedProductFilters = action.payload.filters?.product ?? []; + newState.selectedFilters = action.payload.filters; + newState.selectedComponentFilters = action.payload.filters?.component; + newState.selectedProductFilters = action.payload.filters?.product; newState.activeScreen = 'SEARCH'; return newState; } @@ -452,14 +458,27 @@ function updatePageSize(newState: AppState, action: SetPageSize): AppState { } /** - * Set quick filters. + * Get auto filters. + * + * @param newState - already cloned state that is modified and returned + * @param action - action with payload + * @returns new state with changes + */ +function getAutoFiltersReducer(newState: AppState, action: GetAutoFilters): AppState { + newState.autoFilters = action.payload; + return newState; +} + +/** + * Get custom filters. * * @param newState - already cloned state that is modified and returned * @param action - action with payload * @returns new state with changes */ -function setQuickFiltersReducer(newState: AppState, action: SetQuickFilters): AppState { - newState.quickFilters = action.payload; +function getCustomFiltersReducer(newState: AppState, action: GetCustomFilters): AppState { + console.log(action.payload); + newState.customFilters = action.payload; return newState; } diff --git a/packages/webapp/src/webview/ui/components/Header/Filters/FiltersRibbon.scss b/packages/webapp/src/webview/ui/components/Header/Filters/FiltersRibbon.scss index 4fd87d8a..02edce8c 100644 --- a/packages/webapp/src/webview/ui/components/Header/Filters/FiltersRibbon.scss +++ b/packages/webapp/src/webview/ui/components/Header/Filters/FiltersRibbon.scss @@ -1,31 +1,11 @@ -.clear-filters { - height: 25px; - color: var(--vscode-foreground); - padding: 0 5px 0px 3px; - text-align: start; - cursor: pointer; - background: none; - border: 0; - margin-left: 7px; +.guided-answer__header__filter-ribbon { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + gap: 5px; - >i { - vertical-align: middle; - margin-right: 3px; + &__text { + margin: 0; } - - >span { - text-decoration: underline; - text-underline-offset: 1px; - } -} -.clear-filters:hover { - background: var(--vscode-menubar-selectionBackground); - outline: 1px solid var(--vscode-contrastActiveBorder); - border-radius: 3px; - > span { - text-decoration: none; - } -} -.clear-filters:focus-visible { - outline: 1px solid var(--vscode-focusBorder, #007fd4); } diff --git a/packages/webapp/src/webview/ui/components/Header/Filters/FiltersRibbon.tsx b/packages/webapp/src/webview/ui/components/Header/Filters/FiltersRibbon.tsx index 5e2b9594..82474f12 100644 --- a/packages/webapp/src/webview/ui/components/Header/Filters/FiltersRibbon.tsx +++ b/packages/webapp/src/webview/ui/components/Header/Filters/FiltersRibbon.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; +import i18next from 'i18next'; import type { AppState } from '../../../../types'; import { actions } from '../../../../state'; import { UIIcon, UiIcons } from '@sap-ux/ui-components'; @@ -13,11 +14,20 @@ export function FiltersRibbon() { const appState = useSelector((state) => state); const [selectedProductFilters, setSelectedProductFilters] = useState(appState.selectedProductFilters); const [selectedComponentFilters, setSelectedComponentFilters] = useState(appState.selectedProductFilters); + const hasProductsFilter = selectedProductFilters.length > 0; const hasComponentsFilter = selectedComponentFilters.length > 0; const hasFilters = hasProductsFilter || hasComponentsFilter; const hasBothFilters = hasProductsFilter && hasComponentsFilter; - const resetFilters = () => { + + const isSaved = appState.customFilters.find((f) => { + return ( + (f.component ?? []).toString() === selectedComponentFilters.toString() && + (f.product ?? []).toString() === selectedProductFilters.toString() + ); + }); + + const clearFilters = () => { actions.resetFilters(); actions.searchTree({ query: appState.query, @@ -32,6 +42,16 @@ export function FiltersRibbon() { }); }; + const saveFilters = () => { + actions.updateCustomFilters([ + ...appState.customFilters, + { + component: selectedComponentFilters.length ? undefined : selectedComponentFilters, + product: selectedProductFilters.length ? undefined : selectedProductFilters + } + ]); + }; + useEffect(() => { setSelectedProductFilters( appState.guidedAnswerTreeSearchResult.productFilters @@ -45,24 +65,40 @@ export function FiltersRibbon() { ); }, [appState.selectedProductFilters, appState.selectedComponentFilters]); - return ( - <> - {hasFilters && ( -
- Searching in {hasProductsFilter ? 'Product' : ''} - {((hasProductsFilter && !hasComponentsFilter) || hasBothFilters) &&  } - {selectedProductFilters?.map((pf: string) => pf).join(', ')} - {hasBothFilters &&   and  } - {hasComponentsFilter && !hasProductsFilter &&  } - {hasComponentsFilter ? 'Component' : ''}  - {selectedComponentFilters?.map((cf: string) => cf).join(', ')} - {hasBothFilters &&  } - -
+ return hasFilters ? ( +
+

+ Searching in {hasProductsFilter ? 'Product' : ''} + {((hasProductsFilter && !hasComponentsFilter) || hasBothFilters) &&  } + {selectedProductFilters?.join(', ')} + {hasBothFilters &&   and  } + {hasComponentsFilter && !hasProductsFilter &&  } + {hasComponentsFilter ? 'Component' : ''}  + {selectedComponentFilters?.join(', ')} + {hasBothFilters &&  } +

+ +
+ + {!isSaved && ( + )} - +
+ ) : ( + <> ); } diff --git a/packages/webapp/src/webview/ui/components/Header/Header.scss b/packages/webapp/src/webview/ui/components/Header/Header.scss index 990fd42e..118b80d8 100644 --- a/packages/webapp/src/webview/ui/components/Header/Header.scss +++ b/packages/webapp/src/webview/ui/components/Header/Header.scss @@ -28,11 +28,11 @@ font-size: 2em; } - &__navButtons { + &__button { display: flex; align-items: center; gap: 3px; - margin: 30px 0 0 0; + margin: 0; padding: 5px; color: var(--vscode-foreground); text-align: start; @@ -52,11 +52,13 @@ } &__text { + font-size: 13px; + line-height: 13px; text-decoration: underline; } } - &__navButtons:hover { + &__button:hover { background: var(--vscode-menubar-selectionBackground); outline: 1px solid var(--vscode-contrastActiveBorder); border-radius: 3px; @@ -66,7 +68,7 @@ } } - &__navButtons:focus-visible { + &__button:focus-visible { outline: 1px solid var(--vscode-focusBorder, #007fd4); } @@ -83,12 +85,12 @@ &__restart-feedback-buttons { display: flex; + align-items: center; } &__divider { border-left: 1px solid var(--vscode-editorWidget-border); height: 16px; - margin-top: 35px; margin-left: 10px; margin-right: 10px; } @@ -273,7 +275,7 @@ align-items: start; } - &__navButtons { + &__button { text-align: center; justify-content: center; diff --git a/packages/webapp/src/webview/ui/components/Header/Header.tsx b/packages/webapp/src/webview/ui/components/Header/Header.tsx index 7aae756c..9e5b1e09 100644 --- a/packages/webapp/src/webview/ui/components/Header/Header.tsx +++ b/packages/webapp/src/webview/ui/components/Header/Header.tsx @@ -24,11 +24,12 @@ export function Header(): ReactElement { const appState = useSelector((state) => state); const verticalSearch = appState.activeScreen === 'HOME'; return ( -
+
{appState.activeScreen === 'NODE' ? ( - +
diff --git a/packages/webapp/src/webview/ui/components/Header/NavigationButtons/NavigationButtons.tsx b/packages/webapp/src/webview/ui/components/Header/NavigationButtons/NavigationButtons.tsx index 0713a069..4b90823e 100644 --- a/packages/webapp/src/webview/ui/components/Header/NavigationButtons/NavigationButtons.tsx +++ b/packages/webapp/src/webview/ui/components/Header/NavigationButtons/NavigationButtons.tsx @@ -25,13 +25,13 @@ export const HomeButton = React.memo(function HomeButton() { return ( ); }); @@ -44,13 +44,13 @@ export function BackButton() { return ( ); } @@ -63,13 +63,13 @@ export function RestartButton() { return ( ); } @@ -98,7 +98,7 @@ export function ShareButton() { return treeId ? ( <> { @@ -212,7 +212,7 @@ export function BookmarkButton() { return ( { diff --git a/packages/webapp/src/webview/ui/components/HomeGrid/QuickFilters/QuickFilters.scss b/packages/webapp/src/webview/ui/components/HomeGrid/Filters/Filters.scss similarity index 95% rename from packages/webapp/src/webview/ui/components/HomeGrid/QuickFilters/QuickFilters.scss rename to packages/webapp/src/webview/ui/components/HomeGrid/Filters/Filters.scss index 5b6e8685..28992e7f 100644 --- a/packages/webapp/src/webview/ui/components/HomeGrid/QuickFilters/QuickFilters.scss +++ b/packages/webapp/src/webview/ui/components/HomeGrid/Filters/Filters.scss @@ -1,4 +1,4 @@ -.guided-answer__quick-filter { +.guided-answer__filter { >button { padding: 15px; width: 100%; diff --git a/packages/webapp/src/webview/ui/components/HomeGrid/Filters/Filters.tsx b/packages/webapp/src/webview/ui/components/HomeGrid/Filters/Filters.tsx new file mode 100644 index 00000000..c642effb --- /dev/null +++ b/packages/webapp/src/webview/ui/components/HomeGrid/Filters/Filters.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import type { ReactElement } from 'react'; +import { UIIcon, UiIcons } from '@sap-ux/ui-components'; +import { useSelector } from 'react-redux'; +import type { AppState } from '../../../../types'; +import { FocusZone, FocusZoneDirection } from '@fluentui/react-focus'; +import { actions } from '../../../../state'; +import type { FilterStack } from '@sap/guided-answers-extension-types'; +import './Filters.scss'; + +/** + * Shows list of Filters. + * + * @returns - react elements for Filters view + */ +export function Filters(): ReactElement { + const autoFilters = useSelector((state) => state.autoFilters); + const customFilters = useSelector((state) => state.customFilters); + + const renderFilter = (f: FilterStack) => ( +
  • + +
  • + ); + + return ( +
    +

    + + Filters +

    + +
      + {autoFilters.map(renderFilter)} + {customFilters.map(renderFilter)} +
    +
    +
    + ); +} diff --git a/packages/webapp/src/webview/ui/components/HomeGrid/Filters/index.ts b/packages/webapp/src/webview/ui/components/HomeGrid/Filters/index.ts new file mode 100644 index 00000000..c7b59bcd --- /dev/null +++ b/packages/webapp/src/webview/ui/components/HomeGrid/Filters/index.ts @@ -0,0 +1 @@ +export * from './Filters'; diff --git a/packages/webapp/src/webview/ui/components/HomeGrid/HomeGrid.tsx b/packages/webapp/src/webview/ui/components/HomeGrid/HomeGrid.tsx index 690c9f7a..8b821c21 100644 --- a/packages/webapp/src/webview/ui/components/HomeGrid/HomeGrid.tsx +++ b/packages/webapp/src/webview/ui/components/HomeGrid/HomeGrid.tsx @@ -4,7 +4,7 @@ import { useSelector } from 'react-redux'; import type { AppState } from '../../../types'; import { Bookmarks } from './Bookmarks'; import { LastVisited } from './LastVisited'; -import { QuickFilters } from './QuickFilters'; +import { Filters } from './Filters'; import './HomeGrid.scss'; /** @@ -17,13 +17,13 @@ export function HomeGrid(): ReactElement { const [hasBookmarks, setHasBookmarks] = useState(false); const hasLastVisited = !!appState.lastVisitedGuides.length; - const hasQuickFilters = !!appState.quickFilters.length; + const hasFilters = !!appState.autoFilters.length || !!appState.customFilters.length; if (!hasBookmarks && !!Object.keys(appState.bookmarks).length) { setHasBookmarks(true); } - const isTwoColumnLayout = hasLastVisited && hasBookmarks && hasQuickFilters; + const isTwoColumnLayout = hasLastVisited && hasBookmarks && hasFilters; return isTwoColumnLayout ? (
    @@ -32,7 +32,7 @@ export function HomeGrid(): ReactElement {
    - +
    ) : ( @@ -40,7 +40,7 @@ export function HomeGrid(): ReactElement {
    {hasLastVisited && } {hasBookmarks && } - {hasQuickFilters && } + {hasFilters && }
    ); diff --git a/packages/webapp/src/webview/ui/components/HomeGrid/QuickFilters/QuickFilters.tsx b/packages/webapp/src/webview/ui/components/HomeGrid/QuickFilters/QuickFilters.tsx deleted file mode 100644 index cca90bef..00000000 --- a/packages/webapp/src/webview/ui/components/HomeGrid/QuickFilters/QuickFilters.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import type { ReactElement } from 'react'; -import { UIIcon, UiIcons } from '@sap-ux/ui-components'; -import { useSelector } from 'react-redux'; -import type { AppState } from '../../../../types'; -import { FocusZone, FocusZoneDirection } from '@fluentui/react-focus'; -import { actions } from '../../../../state'; -import type { GuidedAnswersQueryFilterOptions } from '@sap/guided-answers-extension-types'; -import './QuickFilters.scss'; - -/** - * Shows list of Quick Filters. - * - * @returns - react elements for QuickFilters view - */ -export function QuickFilters(): ReactElement { - const filters = useSelector((state) => state.quickFilters); - return ( -
    -

    - - Quick Filters -

    - -
      - {filters.map((f: GuidedAnswersQueryFilterOptions) => ( -
    • - -
    • - ))} -
    -
    -
    - ); -} diff --git a/packages/webapp/src/webview/ui/components/HomeGrid/QuickFilters/index.ts b/packages/webapp/src/webview/ui/components/HomeGrid/QuickFilters/index.ts deleted file mode 100644 index 99ae4174..00000000 --- a/packages/webapp/src/webview/ui/components/HomeGrid/QuickFilters/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './QuickFilters'; diff --git a/packages/webapp/test/HomeGrid/HomeGrid.test.tsx b/packages/webapp/test/HomeGrid/HomeGrid.test.tsx index 82f978ff..2af12813 100644 --- a/packages/webapp/test/HomeGrid/HomeGrid.test.tsx +++ b/packages/webapp/test/HomeGrid/HomeGrid.test.tsx @@ -26,7 +26,7 @@ describe('', () => { } }, lastVisitedGuides: [], - quickFilters: [] + autoFilters: [] }) ); const { container } = render(); @@ -50,19 +50,19 @@ describe('', () => { createdAt: 'time' } ], - quickFilters: [] + autoFilters: [] }) ); const { container } = render(); expect(container).toMatchSnapshot(); }); - it('Should render only QuickFilters', () => { + it('Should render only Filters', () => { (useSelector as jest.Mock).mockImplementation((selector) => selector({ bookmarks: {}, lastVisitedGuides: [], - quickFilters: [ + autoFilters: [ { product: ['product 1'], component: ['component 1'] @@ -102,7 +102,7 @@ describe('', () => { createdAt: 'time' } ], - quickFilters: [ + autoFilters: [ { product: ['product 1'], component: ['component 1'] @@ -130,7 +130,7 @@ describe('', () => { } }, lastVisitedGuides: [], - quickFilters: [] + autoFilters: [] }) ); const { container } = render(); @@ -154,7 +154,7 @@ describe('', () => { createdAt: 'time' } ], - quickFilters: [] + autoFilters: [] }) ); const { container } = render(); diff --git a/packages/webapp/test/HomeGrid/QuickFilters.test.tsx b/packages/webapp/test/HomeGrid/QuickFilters.test.tsx index 483fada6..a3590699 100644 --- a/packages/webapp/test/HomeGrid/QuickFilters.test.tsx +++ b/packages/webapp/test/HomeGrid/QuickFilters.test.tsx @@ -3,7 +3,7 @@ import { render, cleanup } from '@testing-library/react'; import { fireEvent, screen } from '@testing-library/dom'; import { useSelector } from 'react-redux'; import { actions } from '../../src/webview/state'; -import { QuickFilters } from '../../src/webview/ui/components/HomeGrid/QuickFilters'; +import { Filters } from '../../src/webview/ui/components/HomeGrid/Filters'; jest.mock('../../src/webview/state', () => ({ actions: { @@ -16,19 +16,19 @@ jest.mock('react-redux', () => ({ useSelector: jest.fn() })); -describe('', () => { +describe('', () => { afterEach(cleanup); - const mockQuickFilters = [ + const mockAutoFilters = [ { product: ['product 1'], component: ['component 1'] } ]; - it('Should render QuickFilters component', () => { - (useSelector as jest.Mock).mockImplementation((selector) => selector({ quickFilters: mockQuickFilters })); - const { container } = render(); + it('Should render AutoFilters component', () => { + (useSelector as jest.Mock).mockImplementation((selector) => selector({ autoFilters: mockAutoFilters })); + const { container } = render(); expect(container).toMatchSnapshot(); fireEvent.click(screen.getByRole('button')); diff --git a/packages/webapp/test/State/Reducers.test.ts b/packages/webapp/test/State/Reducers.test.ts index 56a4bd79..b0452b50 100644 --- a/packages/webapp/test/State/Reducers.test.ts +++ b/packages/webapp/test/State/Reducers.test.ts @@ -14,7 +14,7 @@ import { RESTORE_STATE, RESET_FILTERS, GO_TO_HOME_PAGE, - SET_QUICK_FILTERS, + SET_AUTO_FILTERS, GUIDE_FEEDBACK, SEARCH_TREE } from '@sap/guided-answers-extension-types'; @@ -69,7 +69,7 @@ const mockedInitState = { pageSize: 20, activeScreen: 'HOME', lastVisitedGuides: [], - quickFilters: [] + autoFilters: [] }; const mockedActiveGuidedAnswerNode = [ @@ -170,7 +170,7 @@ describe('Test functions in reducers', () => { pageSize: 20, activeScreen: 'SEARCH', lastVisitedGuides: [], - quickFilters: [] + autoFilters: [] }; expect(answersWithDefaultState).toEqual(expected); @@ -242,7 +242,7 @@ describe('Test functions in reducers', () => { pageSize: 20, activeScreen: 'NODE', lastVisitedGuides: [], - quickFilters: [] + autoFilters: [] }); const mockedInitStateWithActiveGuidedNode: any = mockedInitState; @@ -274,7 +274,7 @@ describe('Test functions in reducers', () => { pageSize: 20, activeScreen: 'NODE', lastVisitedGuides: [], - quickFilters: [] + autoFilters: [] }); }); @@ -388,14 +388,14 @@ describe('Test functions in reducers', () => { expect(resetSelectedFiltersState.selectedComponentFilters).toEqual([]); }); - it('Should set quick filters', () => { + it('Should set auto filters', () => { const state = getInitialState(); - const setQuickFiltersState = reducer(state, { - type: SET_QUICK_FILTERS, + const setAutoFiltersState = reducer(state, { + type: SET_AUTO_FILTERS, payload: [{ product: ['product'], component: ['component'] }] }); - expect(setQuickFiltersState.quickFilters).toEqual([{ product: ['product'], component: ['component'] }]); + expect(setAutoFiltersState.autoFilters).toEqual([{ product: ['product'], component: ['component'] }]); }); it('Should set filters to search tree', () => {