Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -385,5 +385,128 @@ describe('Create Item Tool Behaviour', () => {
);
});
});

describe('Description Setting', () => {
const successfulDescriptionResponse = {
set_item_description_content: {
success: true,
block_ids: ['block1'],
error: null,
},
};

it('Sets item description after creating a new item', async () => {
mocks.setResponses([successfulCreateItemResponse, successfulDescriptionResponse]);

const tool = new CreateItemTool(mocks.mockApiClient, { boardId: 456 });

const result = await tool.execute({
name: 'Test Item',
columnValues: '{"text_column": "Test Value"}',
description: '## My Description\n\nSome content.',
});

expect(result.content).toEqual({ message: 'Item 123456789 successfully created', item_id: '123456789', item_name: 'New Item', item_url: undefined, board_id: 456 });
expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('mutation setItemDescriptionContent'),
{ itemId: '123456789', markdown: '## My Description\n\nSome content.' },
);
});

it('Skips description call when description is not provided for new item', async () => {
mocks.setResponse(successfulCreateItemResponse);

const tool = new CreateItemTool(mocks.mockApiClient, { boardId: 456 });

await tool.execute({
name: 'Test Item',
columnValues: '{"text_column": "Test Value"}',
});

expect(mocks.getMockRequest()).toHaveBeenCalledTimes(1);
expect(mocks.getMockRequest()).not.toHaveBeenCalledWith(
expect.stringContaining('setItemDescriptionContent'),
expect.anything(),
);
});

it('Sets item description after creating a subitem', async () => {
const successfulCreateSubitemResponse = {
create_subitem: {
id: '111222333',
name: 'New Subitem',
parent_item: { id: '123' },
},
};
mocks.setResponses([successfulCreateSubitemResponse, successfulDescriptionResponse]);

const tool = new CreateItemTool(mocks.mockApiClient, { boardId: 456 });

const result = await tool.execute({
name: 'New Subitem',
columnValues: '{"text_column": "Subitem Value"}',
parentItemId: 123,
description: '## Subitem Description',
});

expect(result.content).toEqual({ message: 'Subitem 111222333 created under 123', item_id: '111222333', item_name: 'New Subitem', item_url: undefined });
expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('mutation setItemDescriptionContent'),
{ itemId: '111222333', markdown: '## Subitem Description' },
);
});

it('Sets item description after duplicating an item', async () => {
mocks.setResponses([successfulDuplicateItemResponse, successfulDescriptionResponse]);
mockChangeColumnValuesTool.execute.mockResolvedValue(successfulUpdateResponse);

const tool = new CreateItemTool(mocks.mockApiClient, { boardId: 456 });

const result = await tool.execute({
name: 'Updated Item',
columnValues: '{"text_column": "Updated Value"}',
duplicateFromItemId: 123,
description: '## Duplicated Description',
});

expect(result.content).toEqual({ message: 'Item 987654321 duplicated from 123', item_id: '987654321', item_name: 'Duplicated Item', item_url: undefined, board_id: 456 });
expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('mutation setItemDescriptionContent'),
{ itemId: '987654321', markdown: '## Duplicated Description' },
);
});

it('Throws error when set_item_description_content returns success: false', async () => {
mocks.setResponses([
successfulCreateItemResponse,
{ set_item_description_content: { success: false, block_ids: null, error: 'Markdown conversion failed' } },
]);

const tool = new CreateItemTool(mocks.mockApiClient, { boardId: 456 });

await expect(
tool.execute({
name: 'Test Item',
columnValues: '{"text_column": "Test Value"}',
description: '## Bad Description',
}),
).rejects.toThrow('Failed to set item description: Markdown conversion failed');
});

it('Throws error when set_item_description_content API call fails', async () => {
mocks.setResponseOnce(successfulCreateItemResponse);
mocks.getMockRequest().mockRejectedValueOnce(new Error('Network error'));

const tool = new CreateItemTool(mocks.mockApiClient, { boardId: 456 });

await expect(
tool.execute({
name: 'Test Item',
columnValues: '{"text_column": "Test Value"}',
description: '## My Description',
}),
).rejects.toThrow('Network error');
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import {
CreateItemMutationVariables,
DuplicateItemMutation,
CreateSubitemMutation,
SetItemDescriptionContentMutation,
SetItemDescriptionContentMutationVariables,
} from '../../../../monday-graphql/generated/graphql/graphql';
import { createItem } from '../../../../monday-graphql/queries.graphql';
import { duplicateItem } from './duplicate-item.graphql';
import { createSubitem } from './create-subitem.graphql';
import { setItemDescriptionContent } from './set-item-description-content.graphql';
import { ToolInputType, ToolOutputType, ToolType } from '../../../tool';
import { BaseMondayApiTool, createMondayApiAnnotations } from '../base-monday-api-tool';
import { ChangeItemColumnValuesTool } from '../change-item-column-values-tool';
Expand All @@ -29,6 +32,10 @@ export const createItemToolSchema = {
.number()
.optional()
.describe('The id of existing item to duplicate and update with new values (only provide when duplicating)'),
description: z
.string()
.optional()
.describe('Markdown content to set as the item description. Replaces any existing description.'),
};

export const createItemInBoardToolSchema = {
Expand All @@ -50,7 +57,7 @@ export class CreateItemTool extends BaseMondayApiTool<CreateItemToolInput> {

getDescription(): string {
return (
'Create a new item with provided values, create a subitem under a parent item, or duplicate an existing item and update it with new values. Use parentItemId when creating a subitem under an existing item. Use duplicateFromItemId when copying an existing item with modifications.' +
'Create a new item with provided values, create a subitem under a parent item, or duplicate an existing item and update it with new values. Use parentItemId when creating a subitem under an existing item. Use duplicateFromItemId when copying an existing item with modifications. Optionally provide description (markdown) to set the item description after creation.' +
`[REQUIRED PRECONDITION]: Before using this tool, if new columns were added to the board or if you are not familiar with the board's structure (column IDs, column types, status labels, etc.), first use get_board_info to understand the board metadata. This is essential for constructing proper column values and knowing which columns are available.`
);
}
Expand Down Expand Up @@ -117,6 +124,10 @@ export class CreateItemTool extends BaseMondayApiTool<CreateItemToolInput> {
columnValues: JSON.stringify(columnValuesAndName),
});

if (input.description) {
await this.setItemDescription(duplicateRes.duplicate_item.id, input.description);
}

return {
content: { message: `Item ${duplicateRes.duplicate_item.id} duplicated from ${input.duplicateFromItemId}`, item_id: duplicateRes.duplicate_item.id, item_name: duplicateRes.duplicate_item.name, item_url: duplicateRes.duplicate_item .url, board_id: boardId },
};
Expand All @@ -138,6 +149,10 @@ export class CreateItemTool extends BaseMondayApiTool<CreateItemToolInput> {
throw new Error('Failed to create subitem: no subitem created');
}

if (input.description) {
await this.setItemDescription(res.create_subitem.id, input.description);
}

return {
content: { message: `Subitem ${res.create_subitem.id} created under ${input.parentItemId}`, item_id: res.create_subitem.id, item_name: res.create_subitem.name, item_url: res.create_subitem.url },
};
Expand All @@ -160,11 +175,26 @@ export class CreateItemTool extends BaseMondayApiTool<CreateItemToolInput> {

const res = await this.mondayApi.request<CreateItemMutation>(createItem, variables);

if (input.description && res.create_item?.id) {
await this.setItemDescription(res.create_item.id, input.description);
}

return {
content: { message: `Item ${res.create_item?.id} successfully created`, item_id: res.create_item?.id, item_name: res.create_item?.name, item_url: res.create_item?.url, board_id: boardId },
};
} catch (error) {
rethrowWithContext(error, 'create item');
}
}

private async setItemDescription(itemId: string, markdown: string): Promise<void> {
const variables: SetItemDescriptionContentMutationVariables = {
itemId,
markdown,
};
const res = await this.mondayApi.request<SetItemDescriptionContentMutation>(setItemDescriptionContent, variables);
if (!res.set_item_description_content?.success) {
throw new Error(`Failed to set item description: ${res.set_item_description_content?.error ?? 'unknown error'}`);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { gql } from 'graphql-request';

export const setItemDescriptionContent = gql`
mutation setItemDescriptionContent($itemId: ID!, $markdown: String!) {
set_item_description_content(item_id: $itemId, markdown: $markdown) {
success
block_ids
error
}
}
`;
Loading
Loading