Skip to content

Commit

Permalink
Merge pull request #2770 from opral/felixhaeberle/sherl-38-adding-cap…
Browse files Browse the repository at this point in the history
…ability-to-generate-message

adding capability to create/extract message from command palette | Sherlock 🕵️‍♂️
  • Loading branch information
felixhaeberle authored May 14, 2024
2 parents c7f53c2 + 6439b4b commit 0df98b7
Show file tree
Hide file tree
Showing 14 changed files with 281 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/little-olives-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vs-code-extension": minor
---

add commands to command palette
8 changes: 8 additions & 0 deletions inlang/source-code/ide-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@
{
"command": "sherlock.toggleInlineAnnotations",
"title": "Sherlock: Toggle Inline Annotations"
},
{
"command": "sherlock.extractMessage",
"title": "Sherlock: Extract Message"
},
{
"command": "sherlock.createMessage",
"title": "Sherlock: Create Message"
}
],
"menus": {
Expand Down
156 changes: 156 additions & 0 deletions inlang/source-code/ide-extension/src/commands/createMessage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { describe, it, beforeEach, afterEach, vi, expect } from "vitest"
import { createMessageCommand } from "./createMessage.js"
import { state } from "../utilities/state.js"
import { msg } from "../utilities/messages/msg.js"
import { window } from "vscode"
import { telemetry } from "../services/telemetry/index.js"
import { CONFIGURATION } from "../configuration.js"

vi.mock("vscode", () => ({
commands: {
registerCommand: vi.fn(),
},
window: {
showInputBox: vi.fn(),
showErrorMessage: vi.fn(),
},
}))

vi.mock("../utilities/state", () => ({
state: vi.fn(),
}))

vi.mock("../utilities/messages/msg", () => ({
msg: vi.fn(),
}))

vi.mock("../services/telemetry/index", () => ({
telemetry: {
capture: vi.fn(),
},
}))

vi.mock("../configuration", () => ({
CONFIGURATION: {
EVENTS: {
ON_DID_CREATE_MESSAGE: {
fire: vi.fn(),
},
},
},
}))

describe("createMessageCommand", () => {
const mockState = {
project: {
settings: vi.fn().mockReturnThis(),
customApi: vi.fn().mockReturnThis(),
query: {
messages: {
create: vi.fn(),
},
},
},
}

beforeEach(() => {
vi.resetAllMocks()
// @ts-expect-error
state.mockReturnValue(mockState)
})

afterEach(() => {
vi.restoreAllMocks()
})

it("should warn if sourceLanguageTag is undefined", async () => {
mockState.project.settings.mockReturnValueOnce({ sourceLanguageTag: undefined })

await createMessageCommand.callback()

expect(msg).toHaveBeenCalledWith(
"The `sourceLanguageTag` is not defined in the project but required to create a message.",
"warn",
"notification"
)
})

it("should return if message content input is cancelled", async () => {
mockState.project.settings.mockReturnValueOnce({ sourceLanguageTag: "en" })
// @ts-expect-error
window.showInputBox.mockResolvedValueOnce(undefined)

await createMessageCommand.callback()

expect(window.showInputBox).toHaveBeenCalledWith({
title: "Enter the message content:",
})
expect(msg).not.toHaveBeenCalled()
})

it("should return if message ID input is cancelled", async () => {
mockState.project.settings.mockReturnValueOnce({ sourceLanguageTag: "en" })
// @ts-expect-error
window.showInputBox.mockResolvedValueOnce("Message content")
// @ts-expect-error
window.showInputBox.mockResolvedValueOnce(undefined)

await createMessageCommand.callback()

expect(window.showInputBox).toHaveBeenCalledWith({
title: "Enter the ID:",
})
expect(msg).not.toHaveBeenCalled()
})

it("should show error message if message creation fails", async () => {
mockState.project.settings.mockReturnValueOnce({ sourceLanguageTag: "en" })
// @ts-expect-error
window.showInputBox.mockResolvedValueOnce("Message content")
// @ts-expect-error
window.showInputBox.mockResolvedValueOnce("messageId")
mockState.project.query.messages.create.mockReturnValueOnce(false)

await createMessageCommand.callback()

expect(window.showErrorMessage).toHaveBeenCalledWith(
"Couldn't upsert new message with id messageId."
)
})

it("should create message and show success message", async () => {
mockState.project.settings.mockReturnValueOnce({ sourceLanguageTag: "en" })
// @ts-expect-error
window.showInputBox.mockResolvedValueOnce("Message content")
// @ts-expect-error
window.showInputBox.mockResolvedValueOnce("messageId")
mockState.project.query.messages.create.mockReturnValueOnce(true)

await createMessageCommand.callback()

expect(mockState.project.query.messages.create).toHaveBeenCalledWith({
data: {
id: "messageId",
alias: {},
selectors: [],
variants: [
{
languageTag: "en",
match: [],
pattern: [
{
type: "Text",
value: "Message content",
},
],
},
],
},
})
expect(CONFIGURATION.EVENTS.ON_DID_CREATE_MESSAGE.fire).toHaveBeenCalled()
expect(telemetry.capture).toHaveBeenCalledWith({
event: "IDE-EXTENSION command executed: Create Message",
})
expect(msg).toHaveBeenCalledWith("Message created.")
})
})
76 changes: 76 additions & 0 deletions inlang/source-code/ide-extension/src/commands/createMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { state } from "../utilities/state.js"
import { msg } from "../utilities/messages/msg.js"
import { commands, window } from "vscode"
import { telemetry } from "../services/telemetry/index.js"
import type { Message } from "@inlang/sdk"
import { CONFIGURATION } from "../configuration.js"

/**
* Helps the user to create messages by prompting for the message content.
*/
export const createMessageCommand = {
command: "sherlock.createMessage",
title: "Sherlock: Create Message",
register: commands.registerCommand,
callback: async function () {
const sourceLanguageTag = state().project.settings().sourceLanguageTag

// guard
if (sourceLanguageTag === undefined) {
return msg(
"The `sourceLanguageTag` is not defined in the project but required to create a message.",
"warn",
"notification"
)
}

const messageValue = await window.showInputBox({
title: "Enter the message content:",
})
if (messageValue === undefined) {
return
}

const messageId = await window.showInputBox({
title: "Enter the ID:",
})
if (messageId === undefined) {
return
}

const message: Message = {
id: messageId,
alias: {},
selectors: [],
variants: [
{
languageTag: sourceLanguageTag,
match: [],
pattern: [
{
type: "Text",
value: messageValue,
},
],
},
],
}

// create message
const success = state().project.query.messages.create({
data: message,
})

if (!success) {
return window.showErrorMessage(`Couldn't upsert new message with id ${messageId}.`)
}

// Emit event to notify that a message was created
CONFIGURATION.EVENTS.ON_DID_CREATE_MESSAGE.fire()

telemetry.capture({
event: "IDE-EXTENSION command executed: Create Message",
})
return msg("Message created.")
},
} as const
16 changes: 14 additions & 2 deletions inlang/source-code/ide-extension/src/commands/extractMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const extractMessageCommand = {
command: "sherlock.extractMessage",
title: "Sherlock: Extract Message",
register: commands.registerTextEditorCommand,
callback: async function (textEditor: TextEditor) {
callback: async function (textEditor: TextEditor | undefined) {
const ideExtension = state().project.customApi()["app.inlang.ideExtension"]
const sourceLanguageTag = state().project.settings().sourceLanguageTag

Expand All @@ -40,6 +40,18 @@ export const extractMessageCommand = {
)
}

if (textEditor === undefined) {
return msg(
"No active text editor found. Please open a file in the editor to extract a message.",
"warn",
"notification"
)
}

if (textEditor.selection.isEmpty) {
return msg("Please select a text to extract in your text editor.", "warn", "notification")
}

const messageId = await window.showInputBox({
title: "Enter the ID:",
})
Expand Down Expand Up @@ -113,7 +125,7 @@ export const extractMessageCommand = {
CONFIGURATION.EVENTS.ON_DID_EXTRACT_MESSAGE.fire()

telemetry.capture({
event: "IDE-EXTENSION command executed",
event: "IDE-EXTENSION command executed: Extract Message",
})
return msg("Message extracted.")
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe("previewLanguageTagCommand", () => {
vi.mock("../configuration.js", () => ({
CONFIGURATION: {
EVENTS: {
ON_DID_CREATE_MESSAGE: { fire: vi.fn() },
ON_DID_EDIT_MESSAGE: { fire: vi.fn() },
ON_DID_EXTRACT_MESSAGE: { fire: vi.fn() },
ON_DID_PREVIEW_LANGUAGE_TAG_CHANGE: { fire: vi.fn() },
Expand Down Expand Up @@ -58,6 +59,7 @@ describe("previewLanguageTagCommand", () => {
})
expect(settings.updateSetting).toHaveBeenCalledWith("previewLanguageTag", "en")
expect(CONFIGURATION.EVENTS.ON_DID_EDIT_MESSAGE.fire).toHaveBeenCalledTimes(1)
expect(CONFIGURATION.EVENTS.ON_DID_CREATE_MESSAGE.fire).toHaveBeenCalledTimes(1)
expect(CONFIGURATION.EVENTS.ON_DID_EXTRACT_MESSAGE.fire).toHaveBeenCalledTimes(1)
expect(CONFIGURATION.EVENTS.ON_DID_PREVIEW_LANGUAGE_TAG_CHANGE.fire).toHaveBeenCalledTimes(1)
})
Expand All @@ -69,6 +71,7 @@ describe("previewLanguageTagCommand", () => {

expect(settings.updateSetting).not.toHaveBeenCalled()
expect(CONFIGURATION.EVENTS.ON_DID_EDIT_MESSAGE.fire).not.toHaveBeenCalled()
expect(CONFIGURATION.EVENTS.ON_DID_CREATE_MESSAGE.fire).not.toHaveBeenCalled()
expect(CONFIGURATION.EVENTS.ON_DID_EXTRACT_MESSAGE.fire).not.toHaveBeenCalled()
expect(CONFIGURATION.EVENTS.ON_DID_PREVIEW_LANGUAGE_TAG_CHANGE.fire).not.toHaveBeenCalled()
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const previewLanguageTagCommand = {

CONFIGURATION.EVENTS.ON_DID_EDIT_MESSAGE.fire()
CONFIGURATION.EVENTS.ON_DID_EXTRACT_MESSAGE.fire()
CONFIGURATION.EVENTS.ON_DID_CREATE_MESSAGE.fire()
CONFIGURATION.EVENTS.ON_DID_PREVIEW_LANGUAGE_TAG_CHANGE.fire(selectedTag)
},
}
3 changes: 3 additions & 0 deletions inlang/source-code/ide-extension/src/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { openInFinkCommand } from "./commands/openInFink.js"
import { extractMessageCommand } from "./commands/extractMessage.js"
import { createMessageCommand } from "./commands/createMessage.js"
import { editMessageCommand } from "./commands/editMessage.js"
import { EventEmitter } from "vscode"
import { openProjectCommand } from "./commands/openProject.js"
Expand All @@ -15,6 +16,7 @@ import { toggleInlineAnnotationsCommand } from "./commands/toggleInlineAnnotatio

export const CONFIGURATION = {
EVENTS: {
ON_DID_CREATE_MESSAGE: new EventEmitter<void>(),
ON_DID_EDIT_MESSAGE: new EventEmitter<void>(),
ON_DID_EXTRACT_MESSAGE: new EventEmitter<void>(),
ON_DID_PROJECT_TREE_VIEW_CHANGE: new EventEmitter<ProjectViewNode | undefined>(),
Expand All @@ -23,6 +25,7 @@ export const CONFIGURATION = {
ON_DID_SETTINGS_VIEW_CHANGE: new EventEmitter<void>(),
},
COMMANDS: {
CREATE_MESSAGE: createMessageCommand,
EDIT_MESSAGE: editMessageCommand,
EXTRACT_MESSAGE: extractMessageCommand,
SET_PREVIEW_LANGUAGETAG: previewLanguageTagCommand,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export async function messagePreview(args: { context: vscode.ExtensionContext })
// update decorations, when message was edited / extracted
CONFIGURATION.EVENTS.ON_DID_EDIT_MESSAGE.event(() => updateDecorations())
CONFIGURATION.EVENTS.ON_DID_EXTRACT_MESSAGE.event(() => updateDecorations())
CONFIGURATION.EVENTS.ON_DID_CREATE_MESSAGE.event(() => updateDecorations())
CONFIGURATION.EVENTS.ON_DID_PREVIEW_LANGUAGE_TAG_CHANGE.event(() => updateDecorations())

vscode.workspace.onDidChangeConfiguration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export type TelemetryEvents =
| "IDE-EXTENSION activated"
| "IDE-EXTENSION code action resolved"
| "IDE-EXTENSION jumped to position in editor"
| "IDE-EXTENSION command executed"
| "IDE-EXTENSION command executed: Extract Message"
| "IDE-EXTENSION command executed: Create Message"
| "IDE-EXTENSION completed add to workspace recommendations"
| "IDE-EXTENSION completed create config file"
| "IDE-EXTENSION loaded project"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ vi.mock("../../configuration.js", () => ({
CONFIGURATION: {
EVENTS: {
ON_DID_EXTRACT_MESSAGE: { event: vi.fn() },
ON_DID_CREATE_MESSAGE: { event: vi.fn() },
ON_DID_EDIT_MESSAGE: { event: vi.fn() },
ON_DID_PROJECT_TREE_VIEW_CHANGE: { event: vi.fn() },
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ export function createMessageWebviewProvider(args: {
)
args.context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(updateMessages))

// if message was created, update webview
args.context.subscriptions.push(
CONFIGURATION.EVENTS.ON_DID_CREATE_MESSAGE.event(() => {
updateMessages()
})
)

// if message was extracted, update webview
args.context.subscriptions.push(
CONFIGURATION.EVENTS.ON_DID_EXTRACT_MESSAGE.event(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ vi.mock("../../configuration.js", () => ({
ON_DID_EDIT_MESSAGE: {
fire: vi.fn(),
},
ON_DID_CREATE_MESSAGE: {
fire: vi.fn(),
},
ON_DID_EXTRACT_MESSAGE: {
fire: vi.fn(),
},
Expand Down
Loading

0 comments on commit 0df98b7

Please sign in to comment.