diff --git a/docs/user-guide/studio-commands.md b/docs/user-guide/studio-commands.md index f738495a..68a4463c 100644 --- a/docs/user-guide/studio-commands.md +++ b/docs/user-guide/studio-commands.md @@ -211,12 +211,15 @@ content-cli push analysis --help ## Pull and Push View Bookmarks -Enable users to pull and push view (board) bookmarks using content-cli. For pulling view bookmarks -you can specify --type (SHARED/ALL/USER), and by default it fetches USER bookmarks: +Enable users to pull and push view (board) bookmarks using content-cli. Views (boards) are +identified by their stable node key (`--rootNodeKeyWithBoardKey` and `--key`) rather than the board id, so +the same command works across teams even after a team-to-team copy regenerates the board id. For +pulling view bookmarks you can specify --type (SHARED/ALL/USER), and by default it fetches USER +bookmarks: ``` // Pull view bookmarks -content-cli pull view-bookmarks --profile my-profile-name --id 73d39112-73ae-4bbe-8051-3c0f14e065ec --type SHARED +content-cli pull view-bookmarks --profile my-profile-name --rootNodeKeyWithBoardKey my-package --key my-board --type SHARED ``` After you have pulled your view bookmarks, @@ -225,5 +228,5 @@ the same command as with pushing other assets in Studio: ``` // Push view bookmarks to Studio -content-cli push view-bookmarks -p my-profile-name --id 73d39112-73ae-4bbe-8051-3c0f14e065ec --file studio_view_bookmarks_39c5bb7b-b486-4230-ab01-854a17ddbff2.json +content-cli push view-bookmarks -p my-profile-name --rootNodeKeyWithBoardKey my-package --key my-board --file studio_view_bookmarks_my-package.my-board.json ``` diff --git a/src/commands/view/module.ts b/src/commands/view/module.ts index 64326c52..ea1e49e3 100644 --- a/src/commands/view/module.ts +++ b/src/commands/view/module.ts @@ -15,24 +15,26 @@ class Module extends IModule { .command("view-bookmarks") .description("Command to pull view bookmarks") .option("--type ", "Type of view bookmarks to pull: USER (default), SHARED, or ALL") - .requiredOption("--id ", "ID of the view (board) to pull bookmarks from") + .requiredOption("--rootNodeKeyWithBoardKey ", "Root node key of the package the view (board) belongs to") + .requiredOption("--key ", "Stable node key of the view (board) to pull bookmarks from") .action(this.pullViewBookmarks); const pushCommand = configurator.command("push"); pushCommand .command("view-bookmarks") .description("Command to push view bookmarks to a board") - .requiredOption("--id ", "ID of the view (board) to push bookmarks into") + .requiredOption("--rootNodeKeyWithBoardKey ", "Root node key of the package the view (board) belongs to") + .requiredOption("--key ", "Stable node key of the view (board) to push bookmarks into") .requiredOption("-f, --file ", "The file to push") .action(this.pushViewBookmarks); } private async pullViewBookmarks(context: Context, command: Command, options: OptionValues): Promise { - await new ViewBookmarksCommandService(context).pullViewBookmarks(options.id, options.type); + await new ViewBookmarksCommandService(context).pullViewBookmarks(options.rootNodeKeyWithBoardKey, options.key, options.type); } private async pushViewBookmarks(context: Context, command: Command, options: OptionValues): Promise { - await new ViewBookmarksCommandService(context).pushViewBookmarks(options.id, options.file); + await new ViewBookmarksCommandService(context).pushViewBookmarks(options.rootNodeKeyWithBoardKey, options.key, options.file); } } diff --git a/src/commands/view/view-bookmarks-command.service.ts b/src/commands/view/view-bookmarks-command.service.ts index 280104b4..16ee1ee8 100644 --- a/src/commands/view/view-bookmarks-command.service.ts +++ b/src/commands/view/view-bookmarks-command.service.ts @@ -11,15 +11,21 @@ export class ViewBookmarksCommandService { this.viewBookmarksManagerFactory = new ViewBookmarksManagerFactory(context); } - public async pullViewBookmarks(boardId: string, type?: string): Promise { + public async pullViewBookmarks(rootNodeKeyWithBoardKey: string, key: string, type?: string): Promise { if (type !== undefined && !ALLOWED_VIEW_BOOKMARK_TYPES.includes(type.toUpperCase())) { - logger.error(new FatalError(`Invalid type "${type}". Allowed values are: ${ALLOWED_VIEW_BOOKMARK_TYPES.join(", ")}.`)); + logger.error( + new FatalError(`Invalid type "${type}". Allowed values are: ${ALLOWED_VIEW_BOOKMARK_TYPES.join(", ")}.`) + ); return; } - await this.viewBookmarksManagerFactory.createViewBookmarksManager(null, boardId, type).pull(); + await this.viewBookmarksManagerFactory + .createViewBookmarksManager(null, rootNodeKeyWithBoardKey, key, type) + .pull(); } - public async pushViewBookmarks(boardId: string, filename: string): Promise { - await this.viewBookmarksManagerFactory.createViewBookmarksManager(filename, boardId).push(); + public async pushViewBookmarks(rootNodeKeyWithBoardKey: string, key: string, filename: string): Promise { + await this.viewBookmarksManagerFactory + .createViewBookmarksManager(filename, rootNodeKeyWithBoardKey, key) + .push(); } } diff --git a/src/commands/view/view-bookmarks.manager-factory.ts b/src/commands/view/view-bookmarks.manager-factory.ts index 8047768d..31c22a29 100644 --- a/src/commands/view/view-bookmarks.manager-factory.ts +++ b/src/commands/view/view-bookmarks.manager-factory.ts @@ -11,9 +11,15 @@ export class ViewBookmarksManagerFactory { this.context = context; } - public createViewBookmarksManager(filename: string, boardId: string, type?: string): ViewBookmarksManager { + public createViewBookmarksManager( + filename: string, + rootNodeKeyWithBoardKey: string, + key: string, + type?: string + ): ViewBookmarksManager { const viewBookmarksManager = new ViewBookmarksManager(this.context); - viewBookmarksManager.boardId = boardId; + viewBookmarksManager.rootNodeKeyWithBoardKey = rootNodeKeyWithBoardKey; + viewBookmarksManager.key = key; type = (type ?? "USER").toUpperCase(); viewBookmarksManager.type = type; diff --git a/src/commands/view/view-bookmarks.manager.ts b/src/commands/view/view-bookmarks.manager.ts index 9f1c736d..0c475927 100644 --- a/src/commands/view/view-bookmarks.manager.ts +++ b/src/commands/view/view-bookmarks.manager.ts @@ -8,7 +8,8 @@ export class ViewBookmarksManager extends BaseManager { private static readonly BASE_URL = "/blueprint/api/bookmarks"; private static readonly VIEW_BOOKMARKS_FILE_PREFIX = "studio_view_bookmarks_"; - private _boardId: string; + private _rootNodeKeyWithBoardKey: string; + private _key: string; private _filePath: string; private _type: string; @@ -24,12 +25,20 @@ export class ViewBookmarksManager extends BaseManager { this._filePath = value; } - public get boardId(): string { - return this._boardId; + public get rootNodeKeyWithBoardKey(): string { + return this._rootNodeKeyWithBoardKey; } - public set boardId(value: string) { - this._boardId = value; + public set rootNodeKeyWithBoardKey(value: string) { + this._rootNodeKeyWithBoardKey = value; + } + + public get key(): string { + return this._key; + } + + public set key(value: string) { + this._key = value; } public get type(): string { @@ -41,10 +50,11 @@ export class ViewBookmarksManager extends BaseManager { } public getConfig(): ManagerConfig { + const keyParams = `rootNodeKeyWithBoardKey=${encodeURIComponent(this.rootNodeKeyWithBoardKey)}&key=${encodeURIComponent(this.key)}`; return { - pushUrl: `${ViewBookmarksManager.BASE_URL}/import?boardId=${encodeURIComponent(this.boardId)}`, - pullUrl: `${ViewBookmarksManager.BASE_URL}/export?boardId=${encodeURIComponent(this.boardId)}&type=${encodeURIComponent(this.type)}`, - exportFileName: `${ViewBookmarksManager.VIEW_BOOKMARKS_FILE_PREFIX}${this.boardId}.json`, + pushUrl: `${ViewBookmarksManager.BASE_URL}/import?${keyParams}`, + pullUrl: `${ViewBookmarksManager.BASE_URL}/export?${keyParams}&type=${encodeURIComponent(this.type)}`, + exportFileName: `${ViewBookmarksManager.VIEW_BOOKMARKS_FILE_PREFIX}${this.rootNodeKeyWithBoardKey}.${this.key}.json`, onPushSuccessMessage: (): string => { return "View Bookmarks were pushed successfully."; }, diff --git a/tests/commands/view/view-bookmarks-module.spec.ts b/tests/commands/view/view-bookmarks-module.spec.ts index 0766defc..491b9a34 100644 --- a/tests/commands/view/view-bookmarks-module.spec.ts +++ b/tests/commands/view/view-bookmarks-module.spec.ts @@ -25,16 +25,16 @@ describe("View Bookmarks Module", () => { .mockImplementation(() => mockService); }); - it("should call pullViewBookmarks with id and type", async () => { - const options: OptionValues = { id: "board-123", type: "SHARED" }; + it("should call pullViewBookmarks with rootNodeKeyWithBoardKey, key and type", async () => { + const options: OptionValues = { rootNodeKeyWithBoardKey: "my-package", key: "my-board", type: "SHARED" }; await (module as any).pullViewBookmarks(testContext, mockCommand, options); - expect(mockService.pullViewBookmarks).toHaveBeenCalledWith("board-123", "SHARED"); + expect(mockService.pullViewBookmarks).toHaveBeenCalledWith("my-package", "my-board", "SHARED"); }); - it("should call pushViewBookmarks with id and file", async () => { - const options: OptionValues = { id: "board-123", file: "bookmarks.json" }; + it("should call pushViewBookmarks with rootNodeKeyWithBoardKey, key and file", async () => { + const options: OptionValues = { rootNodeKeyWithBoardKey: "my-package", key: "my-board", file: "bookmarks.json" }; await (module as any).pushViewBookmarks(testContext, mockCommand, options); - expect(mockService.pushViewBookmarks).toHaveBeenCalledWith("board-123", "bookmarks.json"); + expect(mockService.pushViewBookmarks).toHaveBeenCalledWith("my-package", "my-board", "bookmarks.json"); }); describe("register", () => { diff --git a/tests/commands/view/view-bookmarks.spec.ts b/tests/commands/view/view-bookmarks.spec.ts index b23ad4a0..055356b3 100644 --- a/tests/commands/view/view-bookmarks.spec.ts +++ b/tests/commands/view/view-bookmarks.spec.ts @@ -9,9 +9,11 @@ import { getJsonFromDownloadedFile, writeJsonTempFile } from "../../utls/fs-util describe("View bookmarks", () => { - const boardId = "73d39112-73ae-4bbe-8051-3c0f14e065ec"; - const exportBaseUrl = `https://myTeam.celonis.cloud/blueprint/api/bookmarks/export?boardId=${boardId}`; - const importUrl = `https://myTeam.celonis.cloud/blueprint/api/bookmarks/import?boardId=${boardId}`; + const rootNodeKeyWithBoardKey = "my-package"; + const key = "my-board"; + const keyParams = `rootNodeKeyWithBoardKey=${rootNodeKeyWithBoardKey}&key=${key}`; + const exportBaseUrl = `https://myTeam.celonis.cloud/blueprint/api/bookmarks/export?${keyParams}`; + const importUrl = `https://myTeam.celonis.cloud/blueprint/api/bookmarks/import?${keyParams}`; const bookmarksResponse = [ { bookmark: { name: "My View Bookmark", ownerId: "user-1", userPreferenceId: "pref-1" }, @@ -23,7 +25,7 @@ describe("View bookmarks", () => { it("Should call export API with the default USER type and write the response to a file", async () => { mockAxiosGet(`${exportBaseUrl}&type=USER`, bookmarksResponse); - await new ViewBookmarksCommandService(testContext).pullViewBookmarks(boardId, undefined); + await new ViewBookmarksCommandService(testContext).pullViewBookmarks(rootNodeKeyWithBoardKey, key, undefined); expect(mockedAxiosInstance.get).toHaveBeenCalledWith(`${exportBaseUrl}&type=USER`, expect.anything()); expect(getJsonFromDownloadedFile()).toEqual(bookmarksResponse); @@ -34,7 +36,7 @@ describe("View bookmarks", () => { it("Should call export API with the provided type", async () => { mockAxiosGet(`${exportBaseUrl}&type=SHARED`, bookmarksResponse); - await new ViewBookmarksCommandService(testContext).pullViewBookmarks(boardId, "SHARED"); + await new ViewBookmarksCommandService(testContext).pullViewBookmarks(rootNodeKeyWithBoardKey, key, "SHARED"); expect(mockedAxiosInstance.get).toHaveBeenCalledWith(`${exportBaseUrl}&type=SHARED`, expect.anything()); expect(getJsonFromDownloadedFile()).toEqual(bookmarksResponse); @@ -46,7 +48,7 @@ describe("View bookmarks", () => { mockAxiosPost(importUrl, {}); writeJsonTempFile("bookmarks.json", bookmarksResponse); - await new ViewBookmarksCommandService(testContext).pushViewBookmarks(boardId, "bookmarks.json"); + await new ViewBookmarksCommandService(testContext).pushViewBookmarks(rootNodeKeyWithBoardKey, key, "bookmarks.json"); expect(mockedAxiosInstance.post).toHaveBeenCalledWith(importUrl, expect.anything(), expect.anything()); expect(loggingTestTransport.logMessages.length).toBe(1); @@ -58,7 +60,7 @@ describe("View bookmarks", () => { it("Should report a fatal error when the push file does not exist", () => { const exitSpy = jest.spyOn(process, "exit").mockImplementation((() => undefined) as never); - new ViewBookmarksManagerFactory(testContext).createViewBookmarksManager("missing.json", boardId); + new ViewBookmarksManagerFactory(testContext).createViewBookmarksManager("missing.json", rootNodeKeyWithBoardKey, key); expect(exitSpy).toHaveBeenCalledWith(1); });