Skip to content

Commit c95cefe

Browse files
oliversberzsckoutsiarisKlaus-Keller
authored
Feat/ga/last visited (#542)
* feat(ga): first buggy version * feat(ga): switch to array * feat(ga): update list change * feat(ga): update list change * feat(232): add last visited home page section * Create breezy-crabs-peel.md * feat(232): fix code smells * Update packages/webapp/src/webview/state/reducers.ts Co-authored-by: Klaus Keller <[email protected]> * Update packages/ide-extension/src/extension.ts Co-authored-by: Klaus Keller <[email protected]> * feat(232): fix issues --------- Co-authored-by: Christos Koutsiaris <[email protected]> Co-authored-by: Klaus Keller <[email protected]>
1 parent edccde6 commit c95cefe

File tree

25 files changed

+614
-186
lines changed

25 files changed

+614
-186
lines changed

.changeset/breezy-crabs-peel.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"sap-guided-answers-extension": minor
3+
"@sap/guided-answers-extension-types": minor
4+
"@sap/guided-answers-extension-webapp": minor
5+
---
6+
7+
Feat/ga/last visited

packages/ide-extension/src/extension.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { initTelemetry } from './telemetry';
77
import { GuidedAnswersUriHandler } from './links';
88
import type { StartOptions } from './types';
99
import { initBookmarks } from './bookmarks';
10+
import { initLastVisited } from './last-visited';
1011

1112
/**
1213
* Activate function is called by VSCode when the extension gets active.
@@ -21,8 +22,11 @@ export function activate(context: ExtensionContext): void {
2122
}
2223
try {
2324
initBookmarks(context.globalState);
25+
initLastVisited(context.globalState);
2426
} catch (error) {
25-
logString(`Error during initialization of bookmarks.\n${error?.toString()}`);
27+
logString(
28+
`Error during initialization of functionality that relies on VSCode's global state storage.\n${error?.toString()}`
29+
);
2630
}
2731
context.subscriptions.push(
2832
commands.registerCommand('sap.ux.guidedAnswer.openGuidedAnswer', async (startOptions?: StartOptions) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { getAllLastVisitedGuides, initLastVisited, updateLastVisitedGuides } from './lastVisited';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { Memento } from 'vscode';
2+
import type { LastVisitedGuide } from '@sap/guided-answers-extension-types';
3+
import { logString } from '../logger/logger';
4+
5+
let globalStateApi: Memento;
6+
7+
/**
8+
* Initialize the Last Visited functionality by passing VSCode global state.
9+
*
10+
* @param globalState - VSCodes global state for extension
11+
*/
12+
export function initLastVisited(globalState: Memento) {
13+
globalStateApi = globalState;
14+
}
15+
16+
/**
17+
* Return the list of stored last visited guides.
18+
*
19+
* @returns - list of last visited guides
20+
*/
21+
export function getAllLastVisitedGuides(): LastVisitedGuide[] {
22+
return globalStateApi?.get<LastVisitedGuide[]>('lastVisitedGuides') ?? [];
23+
}
24+
25+
/**
26+
* Update the stored last visited guides. If an empty array is passed, all last visited guides are deleted.
27+
*
28+
* @param lastVisitedGuides - array of last visited guides, if empty all last visited guides will be deleted
29+
*/
30+
export function updateLastVisitedGuides(lastVisitedGuides: LastVisitedGuide[]): void {
31+
globalStateApi
32+
.update('lastVisitedGuides', lastVisitedGuides)
33+
.then(undefined, (error) => logString(`Error updating lastVisitedGuides.\n${error?.toString()}`));
34+
}

packages/ide-extension/src/panel/guidedAnswersPanel.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
SEND_FEEDBACK_COMMENT,
1818
SYNCHRONIZE_BOOKMARK,
1919
UPDATE_BOOKMARKS,
20+
UPDATE_LAST_VISITED_GUIDES,
2021
updateGuidedAnswerTrees,
2122
updateActiveNode,
2223
updateNetworkStatus,
@@ -32,7 +33,8 @@ import {
3233
feedbackResponse,
3334
getBookmarks,
3435
goToAllAnswers,
35-
updateBookmark
36+
updateBookmark,
37+
getLastVisitedGuides
3638
} from '@sap/guided-answers-extension-types';
3739
import { getFiltersForIde, getGuidedAnswerApi } from '@sap/guided-answers-extension-core';
3840
import { getHtml } from './html';
@@ -42,6 +44,7 @@ import type { Options, StartOptions } from '../types';
4244
import { setCommonProperties, trackAction, trackEvent } from '../telemetry';
4345
import { extractLinkInfo, generateExtensionLink, generateWebLink } from '../links/link-info';
4446
import { getAllBookmarks, updateBookmarks } from '../bookmarks';
47+
import { updateLastVisitedGuides, getAllLastVisitedGuides } from '../last-visited';
4548

4649
/**
4750
* Class that represents the Guided Answers panel, which hosts the webview UI.
@@ -166,6 +169,7 @@ export class GuidedAnswersPanel {
166169
);
167170
this.postActionToWebview(updateNetworkStatus('OK'));
168171
this.postActionToWebview(getBookmarks(getAllBookmarks()));
172+
this.postActionToWebview(getLastVisitedGuides(getAllLastVisitedGuides()));
169173
}
170174

171175
/**
@@ -364,6 +368,11 @@ export class GuidedAnswersPanel {
364368
updateBookmarks(action.payload.bookmarks);
365369
break;
366370
}
371+
case UPDATE_LAST_VISITED_GUIDES: {
372+
updateLastVisitedGuides(action.payload);
373+
this.postActionToWebview(getLastVisitedGuides(getAllLastVisitedGuides()));
374+
break;
375+
}
367376
case SYNCHRONIZE_BOOKMARK: {
368377
this.synchronizeBookmark(action.payload).catch((error) =>
369378
logString(`Error during synchronizing bookmark\n${error?.toString()}`)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { Memento } from 'vscode';
2+
import { LastVisitedGuide } from '@sap/guided-answers-extension-types';
3+
import { logString } from '../../src/logger/logger';
4+
import { initLastVisited, getAllLastVisitedGuides, updateLastVisitedGuides } from '../../src/last-visited';
5+
6+
jest.mock('../../src/logger/logger', () => ({
7+
logString: jest.fn()
8+
}));
9+
10+
let mockGlobalState: jest.Mocked<Memento>;
11+
12+
const mockLastVisited: LastVisitedGuide[] = [
13+
{
14+
tree: {
15+
TREE_ID: 1,
16+
TITLE: 'Bookmark 1 Title',
17+
DESCRIPTION: 'Bookmark 1 Description',
18+
PRODUCT: 'Product 1, Product 2',
19+
COMPONENT: 'Component 1, Component 2'
20+
},
21+
nodePath: [{ NODE_ID: 1, TITLE: 'Node 1' }],
22+
createdAt: 'time'
23+
},
24+
{
25+
tree: {
26+
TREE_ID: 2,
27+
TITLE: 'Bookmark 2 Title',
28+
DESCRIPTION: 'Bookmark 2 Description',
29+
PRODUCT: 'Product 1, Product 2',
30+
COMPONENT: 'Component 1, Component 2'
31+
},
32+
nodePath: [{ NODE_ID: 2, TITLE: 'Node 2' }],
33+
createdAt: 'time2'
34+
}
35+
] as LastVisitedGuide[];
36+
37+
describe('LastVisited functions', () => {
38+
beforeEach(() => {
39+
mockGlobalState = {
40+
get: jest.fn(),
41+
update: jest.fn(() => Promise.resolve())
42+
} as unknown as jest.Mocked<Memento>;
43+
});
44+
45+
test('get all last visited', () => {
46+
mockGlobalState.get.mockReturnValue(mockLastVisited);
47+
48+
initLastVisited(mockGlobalState);
49+
const lastVisited = getAllLastVisitedGuides();
50+
expect(lastVisited).toEqual(mockLastVisited);
51+
});
52+
53+
test('update last visited', async () => {
54+
initLastVisited(mockGlobalState);
55+
await updateLastVisitedGuides(mockLastVisited);
56+
57+
expect(mockGlobalState.update).toHaveBeenCalledWith('lastVisitedGuides', mockLastVisited);
58+
});
59+
60+
test('log error when updating last visited fails', async () => {
61+
const error = new Error('Update error');
62+
mockGlobalState.update.mockReturnValue(Promise.reject(error));
63+
64+
initLastVisited(mockGlobalState);
65+
await updateLastVisitedGuides(mockLastVisited);
66+
67+
expect(logString).toHaveBeenCalledWith(`Error updating lastVisitedGuides.\n${error.toString()}`);
68+
});
69+
});

packages/ide-extension/test/panel/guidedAnswersPanel.test.ts

+46-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import {
2020
GET_BOOKMARKS,
2121
AppState,
2222
SYNCHRONIZE_BOOKMARK,
23-
UPDATE_BOOKMARKS
23+
UPDATE_BOOKMARKS,
24+
GET_LAST_VISITED_GUIDES,
25+
UPDATE_LAST_VISITED_GUIDES
2426
} from '@sap/guided-answers-extension-types';
2527
import type {
2628
Bookmarks,
@@ -31,13 +33,15 @@ import type {
3133
GuidedAnswerNodeId,
3234
GuidedAnswerTree,
3335
GuidedAnswerTreeId,
34-
GuidedAnswerTreeSearchResult
36+
GuidedAnswerTreeSearchResult,
37+
LastVisitedGuide
3538
} from '@sap/guided-answers-extension-types';
3639
import { GuidedAnswersPanel, GuidedAnswersSerializer } from '../../src/panel/guidedAnswersPanel';
3740
import * as logger from '../../src/logger/logger';
3841
import * as telemetry from '../../src/telemetry';
3942
import type { StartOptions } from '../../src/types';
4043
import { initBookmarks } from '../../src/bookmarks';
44+
import { initLastVisited } from '../../src/last-visited';
4145

4246
type WebviewMessageCallback = (action: GuidedAnswerActions) => void;
4347

@@ -192,7 +196,8 @@ describe('GuidedAnswersPanel', () => {
192196
[{ type: UPDATE_ACTIVE_NODE, payload: { NODE_ID: 1234, TITLE: 'Node 1234' } }],
193197
[{ type: BETA_FEATURES, payload: false }],
194198
[{ type: UPDATE_NETWORK_STATUS, payload: 'OK' }],
195-
[{ type: GET_BOOKMARKS, payload: {} }]
199+
[{ type: GET_BOOKMARKS, payload: {} }],
200+
[{ type: GET_LAST_VISITED_GUIDES, payload: [] }]
196201
]);
197202
});
198203

@@ -219,7 +224,8 @@ describe('GuidedAnswersPanel', () => {
219224
[{ type: UPDATE_ACTIVE_NODE, payload: { NODE_ID: 300 } }],
220225
[{ type: BETA_FEATURES, payload: false }],
221226
[{ type: UPDATE_NETWORK_STATUS, payload: 'OK' }],
222-
[{ type: GET_BOOKMARKS, payload: {} }]
227+
[{ type: GET_BOOKMARKS, payload: {} }],
228+
[{ type: GET_LAST_VISITED_GUIDES, payload: [] }]
223229
]);
224230
});
225231

@@ -728,6 +734,42 @@ describe('GuidedAnswersPanel', () => {
728734
expect(globalStateMock.update).toBeCalledWith('bookmark', expectBookmarks);
729735
});
730736

737+
test('GuidedAnswersPanel communication UPDATE_LAST_VISITED_GUIDES', async () => {
738+
// Mock setup
739+
let onDidReceiveMessageMock: WebviewMessageCallback = () => { };
740+
jest.spyOn(window, 'createWebviewPanel').mockImplementation(() =>
741+
getWebViewPanelMock((callback: WebviewMessageCallback) => {
742+
onDidReceiveMessageMock = callback;
743+
})
744+
);
745+
const lastVisitedMock = [
746+
{
747+
tree: {
748+
TREE_ID: 1,
749+
TITLE: 'Bookmark Title',
750+
},
751+
nodePath: [{ NODE_ID: 2 }, { NODE_ID: 3 }],
752+
createdAt: '2023-05-23T15:41:00.478Z'
753+
}
754+
] as LastVisitedGuide[];
755+
756+
const globalStateMock = {
757+
update: jest.fn()
758+
} as Partial<Memento>;
759+
initLastVisited(globalStateMock as Memento);
760+
761+
// Test execution
762+
const panel = new GuidedAnswersPanel();
763+
panel.show();
764+
await onDidReceiveMessageMock({
765+
type: UPDATE_LAST_VISITED_GUIDES,
766+
payload: lastVisitedMock
767+
});
768+
769+
// Result check
770+
expect(globalStateMock.update).toBeCalledWith('lastVisitedGuides', lastVisitedMock);
771+
});
772+
731773
test('GuidedAnswersPanel communication unhandled action', async () => {
732774
// Mock setup
733775
let onDidReceiveMessageMock: WebviewMessageCallback = () => {};

packages/types/src/actions.ts

+15
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ import type {
4141
GetBookmarks,
4242
Bookmark,
4343
SynchronizeBookmark,
44+
LastVisitedGuide,
45+
GetLastVisitedGuides,
46+
UpdateLastVisitedGuides,
4447
UpdateBookmarksPayload,
4548
GoToHomePage
4649
} from './types';
@@ -76,6 +79,8 @@ import {
7679
UPDATE_BOOKMARKS,
7780
GET_BOOKMARKS,
7881
SYNCHRONIZE_BOOKMARK,
82+
GET_LAST_VISITED_GUIDES,
83+
UPDATE_LAST_VISITED_GUIDES,
7984
GO_TO_HOME_PAGE
8085
} from './types';
8186

@@ -224,3 +229,13 @@ export const synchronizeBookmark = (payload: Bookmark): SynchronizeBookmark => (
224229
type: SYNCHRONIZE_BOOKMARK,
225230
payload
226231
});
232+
233+
export const getLastVisitedGuides = (payload: LastVisitedGuide[]): GetLastVisitedGuides => ({
234+
type: GET_LAST_VISITED_GUIDES,
235+
payload
236+
});
237+
238+
export const updateLastVisitedGuide = (payload: LastVisitedGuide[]): UpdateLastVisitedGuides => ({
239+
type: UPDATE_LAST_VISITED_GUIDES,
240+
payload
241+
});

packages/types/src/types.ts

+21
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ export interface UpdateBookmarksPayload {
122122

123123
export type Bookmarks = Record<string, Bookmark>; //key is 'TREE_ID-NODE_ID:NODE_ID:NODE_ID:...NODE_ID'
124124

125+
export interface LastVisitedGuide {
126+
tree: GuidedAnswerTree;
127+
nodePath: GuidedAnswerNode[];
128+
createdAt: string;
129+
}
130+
125131
export interface GuidedAnswerAPI {
126132
getApiInfo: () => { host: string; version: string };
127133
getNodeById: (id: GuidedAnswerNodeId) => Promise<GuidedAnswerNode>;
@@ -215,6 +221,8 @@ export type GuidedAnswerActions =
215221
| SetQueryValue
216222
| SynchronizeBookmark
217223
| UpdateActiveNodeSharing
224+
| GetLastVisitedGuides
225+
| UpdateLastVisitedGuides
218226
| UpdateBookmarks
219227
| ResetFilters
220228
| RestartAnswer
@@ -242,6 +250,7 @@ export interface AppState {
242250
feedbackResponse: boolean;
243251
bookmarks: Bookmarks;
244252
activeScreen: 'HOME' | 'SEARCH' | 'NODE';
253+
lastVisitedGuides: LastVisitedGuide[];
245254
}
246255

247256
export const UPDATE_GUIDED_ANSWER_TREES = 'UPDATE_GUIDED_ANSWER_TREES';
@@ -359,6 +368,18 @@ export interface UpdateBookmarks {
359368
payload: UpdateBookmarksPayload;
360369
}
361370

371+
export const GET_LAST_VISITED_GUIDES = 'GET_LAST_VISITED_GUIDES';
372+
export interface GetLastVisitedGuides {
373+
type: typeof GET_LAST_VISITED_GUIDES;
374+
payload: LastVisitedGuide[];
375+
}
376+
377+
export const UPDATE_LAST_VISITED_GUIDES = 'UPDATE_LAST_VISITED_GUIDES';
378+
export interface UpdateLastVisitedGuides {
379+
type: typeof UPDATE_LAST_VISITED_GUIDES;
380+
payload: LastVisitedGuide[];
381+
}
382+
362383
export const SET_PRODUCT_FILTERS = 'SET_PRODUCT_FILTERS';
363384
export interface SetProductFilters {
364385
type: typeof SET_PRODUCT_FILTERS;

packages/webapp/src/webview/state/actions.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export {
2626
shareLinkTelemetry,
2727
openLinkTelemetry,
2828
getBookmarks,
29+
getLastVisitedGuides,
2930
updateBookmark,
30-
synchronizeBookmark
31+
synchronizeBookmark,
32+
updateLastVisitedGuide
3133
} from '@sap/guided-answers-extension-types';

0 commit comments

Comments
 (0)