-
Notifications
You must be signed in to change notification settings - Fork 1
BINFR-6408: Add pull/push view-bookmarks commands #372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Manjinder Singh (manjindersingh98)
wants to merge
8
commits into
main
Choose a base branch
from
add-view-bookmarks
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
c022d18
Add pull/push view-bookmarks commands
manjindersingh98 c00a32a
fix: review PR comments
manjindersingh98 981177c
Add tests for view-bookmarks commands
manjindersingh98 23c406a
fix: address sonar issues
manjindersingh98 333ad7e
fix: tests
manjindersingh98 eb9122e
fix: address PR comments
manjindersingh98 9c26621
fix: instructions comment
manjindersingh98 036ae20
fix: variable name
manjindersingh98 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /** | ||
| * Commands related to the View feature. | ||
| */ | ||
|
|
||
| import { Configurator, IModule } from "../../core/command/module-handler"; | ||
| import { Context } from "../../core/command/cli-context"; | ||
| import { Command, OptionValues } from "commander"; | ||
| import { ViewBookmarksCommandService } from "./view-bookmarks-command.service"; | ||
|
|
||
| class Module extends IModule { | ||
|
|
||
| public register(context: Context, configurator: Configurator): void { | ||
| const pullCommand = configurator.command("pull"); | ||
| pullCommand | ||
| .command("view-bookmarks") | ||
| .description("Command to pull view bookmarks") | ||
| .option("--type <type>", "Type of view bookmarks to pull: USER (default), SHARED, or ALL") | ||
| .requiredOption("--id <id>", "ID 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>", "ID of the view (board) to push bookmarks into") | ||
| .requiredOption("-f, --file <file>", "The file to push") | ||
| .action(this.pushViewBookmarks); | ||
| } | ||
|
|
||
| private async pullViewBookmarks(context: Context, command: Command, options: OptionValues): Promise<void> { | ||
| await new ViewBookmarksCommandService(context).pullViewBookmarks(options.id, options.type); | ||
| } | ||
|
|
||
| private async pushViewBookmarks(context: Context, command: Command, options: OptionValues): Promise<void> { | ||
| await new ViewBookmarksCommandService(context).pushViewBookmarks(options.id, options.file); | ||
| } | ||
| } | ||
|
|
||
| export = Module; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { ViewBookmarksManagerFactory } from "./view-bookmarks.manager-factory"; | ||
| import { Context } from "../../core/command/cli-context"; | ||
|
|
||
| export class ViewBookmarksCommandService { | ||
| private readonly viewBookmarksManagerFactory: ViewBookmarksManagerFactory; | ||
|
|
||
| constructor(context: Context) { | ||
| this.viewBookmarksManagerFactory = new ViewBookmarksManagerFactory(context); | ||
| } | ||
|
|
||
| public async pullViewBookmarks(boardId: string, type?: string): Promise<void> { | ||
| await this.viewBookmarksManagerFactory.createViewBookmarksManager(null, boardId, type).pull(); | ||
| } | ||
|
|
||
| public async pushViewBookmarks(boardId: string, filename: string): Promise<void> { | ||
| await this.viewBookmarksManagerFactory.createViewBookmarksManager(filename, boardId).push(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import * as fs from "node:fs"; | ||
| import * as path from "node:path"; | ||
| import { ViewBookmarksManager } from "./view-bookmarks.manager"; | ||
| import { FatalError, logger } from "../../core/utils/logger"; | ||
| import { Context } from "../../core/command/cli-context"; | ||
|
|
||
| export class ViewBookmarksManagerFactory { | ||
| private readonly context: Context; | ||
|
|
||
| constructor(context: Context) { | ||
| this.context = context; | ||
| } | ||
|
|
||
| public createViewBookmarksManager(filename: string, boardId: string, type?: string): ViewBookmarksManager { | ||
| const viewBookmarksManager = new ViewBookmarksManager(this.context); | ||
| viewBookmarksManager.boardId = boardId; | ||
| type = (type ?? "USER").toUpperCase(); | ||
|
|
||
| viewBookmarksManager.type = type; | ||
| if (filename !== null) { | ||
| viewBookmarksManager.filePath = this.resolveFilePath(filename); | ||
| } | ||
| return viewBookmarksManager; | ||
| } | ||
|
|
||
| private resolveFilePath(fileName: string): string { | ||
| if (!fs.existsSync(path.resolve(process.cwd(), fileName))) { | ||
| logger.error(new FatalError("The provided file does not exist")); | ||
| } | ||
| return path.resolve(process.cwd(), fileName); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import * as fs from "node:fs"; | ||
| import * as FormData from "form-data"; | ||
| import { Context } from "../../core/command/cli-context"; | ||
| import { BaseManager } from "../../core/http/http-shared/base.manager"; | ||
| import { ManagerConfig } from "../../core/http/http-shared/manager-config.interface"; | ||
|
|
||
| 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 _filePath: string; | ||
| private _type: string; | ||
|
|
||
| constructor(context: Context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public get filePath(): string { | ||
| return this._filePath; | ||
| } | ||
|
|
||
| public set filePath(value: string) { | ||
| this._filePath = value; | ||
| } | ||
|
|
||
| public get boardId(): string { | ||
| return this._boardId; | ||
| } | ||
|
|
||
| public set boardId(value: string) { | ||
| this._boardId = value; | ||
| } | ||
|
|
||
| public get type(): string { | ||
| return this._type; | ||
| } | ||
|
|
||
| public set type(value: string) { | ||
| this._type = value; | ||
| } | ||
|
|
||
| public getConfig(): ManagerConfig { | ||
| 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`, | ||
| onPushSuccessMessage: (): string => { | ||
| return "View Bookmarks were pushed successfully."; | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| public getBody(): any { | ||
| const formData = new FormData(); | ||
| formData.append("file", fs.createReadStream(this.filePath)); | ||
| return formData; | ||
| } | ||
|
|
||
| protected getSerializedFileContent(data: any): string { | ||
| return JSON.stringify(data); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import Module = require("../../../src/commands/view/module"); | ||
| import { Command, OptionValues } from "commander"; | ||
| import { ViewBookmarksCommandService } from "../../../src/commands/view/view-bookmarks-command.service"; | ||
| import { testContext } from "../../utls/test-context"; | ||
| import { createMockConfigurator } from "../../utls/configurator-mock"; | ||
|
|
||
| jest.mock("../../../src/commands/view/view-bookmarks-command.service"); | ||
|
|
||
| describe("View Bookmarks Module", () => { | ||
| let module: Module; | ||
| let mockCommand: Command; | ||
| let mockService: jest.Mocked<ViewBookmarksCommandService>; | ||
|
|
||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| module = new Module(); | ||
| mockCommand = {} as Command; | ||
|
|
||
| mockService = { | ||
| pullViewBookmarks: jest.fn().mockResolvedValue(undefined), | ||
| pushViewBookmarks: jest.fn().mockResolvedValue(undefined), | ||
| } as any; | ||
|
|
||
| (ViewBookmarksCommandService as jest.MockedClass<typeof ViewBookmarksCommandService>) | ||
| .mockImplementation(() => mockService); | ||
| }); | ||
|
|
||
| it("should call pullViewBookmarks with id and type", async () => { | ||
| const options: OptionValues = { id: "board-123", type: "SHARED" }; | ||
| await (module as any).pullViewBookmarks(testContext, mockCommand, options); | ||
| expect(mockService.pullViewBookmarks).toHaveBeenCalledWith("board-123", "SHARED"); | ||
| }); | ||
|
|
||
| it("should call pushViewBookmarks with id and file", async () => { | ||
| const options: OptionValues = { id: "board-123", file: "bookmarks.json" }; | ||
| await (module as any).pushViewBookmarks(testContext, mockCommand, options); | ||
| expect(mockService.pushViewBookmarks).toHaveBeenCalledWith("board-123", "bookmarks.json"); | ||
| }); | ||
|
|
||
| describe("register", () => { | ||
| it("registers the pull and push command groups without throwing", () => { | ||
| const mockConfigurator = createMockConfigurator(); | ||
|
|
||
| expect(() => new Module().register(testContext, mockConfigurator)).not.toThrow(); | ||
|
|
||
| expect(mockConfigurator.command).toHaveBeenCalledWith("pull"); | ||
| expect(mockConfigurator.command).toHaveBeenCalledWith("push"); | ||
| }); | ||
|
|
||
| it("wires an action handler for every leaf subcommand", () => { | ||
| const mockConfigurator = createMockConfigurator(); | ||
|
|
||
| new Module().register(testContext, mockConfigurator); | ||
|
|
||
| // pull view-bookmarks + push view-bookmarks | ||
| const expectedLeafCommands = 2; | ||
| expect(mockConfigurator.action).toHaveBeenCalledTimes(expectedLeafCommands); | ||
| for (const call of mockConfigurator.action.mock.calls) { | ||
| expect(typeof call[0]).toBe("function"); | ||
| } | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import * as fs from "node:fs"; | ||
| import * as path from "node:path"; | ||
| import { mockAxiosGet, mockAxiosPost, mockedAxiosInstance } from "../../utls/http-requests-mock"; | ||
| import { mockCreateReadStream, mockExistsSync } from "../../utls/fs-mock-utils"; | ||
| import { ViewBookmarksCommandService } from "../../../src/commands/view/view-bookmarks-command.service"; | ||
| import { ViewBookmarksManagerFactory } from "../../../src/commands/view/view-bookmarks.manager-factory"; | ||
| import { loggingTestTransport, mockWriteFileSync } from "../../jest.setup"; | ||
| import { testContext } from "../../utls/test-context"; | ||
|
|
||
| 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 bookmarksResponse = [ | ||
| { | ||
| bookmark: { name: "My View Bookmark", ownerId: "user-1", userPreferenceId: "pref-1" }, | ||
| preference: { id: "pref-1", value: "{}" }, | ||
| }, | ||
| ]; | ||
|
|
||
| describe("pull", () => { | ||
| 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); | ||
|
|
||
| expect(mockedAxiosInstance.get).toHaveBeenCalledWith(`${exportBaseUrl}&type=USER`, expect.anything()); | ||
| expect(mockWriteFileSync).toHaveBeenCalledWith( | ||
| path.resolve(process.cwd(), `studio_view_bookmarks_${boardId}.json`), | ||
| JSON.stringify(bookmarksResponse), | ||
| { encoding: "utf-8", mode: 0o600 } | ||
| ); | ||
| expect(loggingTestTransport.logMessages.length).toBe(1); | ||
| expect(loggingTestTransport.logMessages[0].message).toContain("File downloaded successfully. New filename: "); | ||
| }); | ||
|
|
||
| it("Should call export API with the provided type", async () => { | ||
| mockAxiosGet(`${exportBaseUrl}&type=SHARED`, bookmarksResponse); | ||
|
|
||
| await new ViewBookmarksCommandService(testContext).pullViewBookmarks(boardId, "SHARED"); | ||
|
|
||
| expect(mockedAxiosInstance.get).toHaveBeenCalledWith(`${exportBaseUrl}&type=SHARED`, expect.anything()); | ||
| expect(mockWriteFileSync).toHaveBeenCalledWith( | ||
| path.resolve(process.cwd(), `studio_view_bookmarks_${boardId}.json`), | ||
| JSON.stringify(bookmarksResponse), | ||
| { encoding: "utf-8", mode: 0o600 } | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe("push", () => { | ||
| it("Should call import API with the file as multipart body", async () => { | ||
| mockAxiosPost(importUrl, {}); | ||
| mockExistsSync(); | ||
| mockCreateReadStream(Buffer.from(JSON.stringify(bookmarksResponse))); | ||
|
|
||
| await new ViewBookmarksCommandService(testContext).pushViewBookmarks(boardId, "bookmarks.json"); | ||
|
|
||
| expect(mockedAxiosInstance.post).toHaveBeenCalledWith(importUrl, expect.anything(), expect.anything()); | ||
| expect(loggingTestTransport.logMessages.length).toBe(1); | ||
| expect(loggingTestTransport.logMessages[0].message).toContain("View Bookmarks were pushed successfully."); | ||
| }); | ||
| }); | ||
|
|
||
| describe("manager factory", () => { | ||
| it("Should report a fatal error when the push file does not exist", () => { | ||
| (fs.existsSync as jest.Mock).mockReturnValue(false); | ||
| const exitSpy = jest.spyOn(process, "exit").mockImplementation((() => undefined) as never); | ||
|
|
||
| new ViewBookmarksManagerFactory(testContext).createViewBookmarksManager("missing.json", boardId); | ||
|
|
||
| expect(exitSpy).toHaveBeenCalledWith(1); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.