Skip to content

Commit 9a05a8a

Browse files
authored
feat: add preset selector in TasksPage (#19012)
* Adds a preset selector in TasksPage with the default preset pre-selected and at the top of the list. * If no default preset exists, the user is prompted to select one. * If a preset defines an AI Prompt, it will override the textarea.
1 parent 5c31b98 commit 9a05a8a

File tree

3 files changed

+364
-89
lines changed

3 files changed

+364
-89
lines changed

site/src/pages/TasksPage/TasksPage.stories.tsx

Lines changed: 89 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { API } from "api/api";
44
import { MockUsers } from "pages/UsersPage/storybookData/users";
55
import { reactRouterParameters } from "storybook-addon-remix-react-router";
66
import {
7+
MockAIPromptPresets,
8+
MockNewTaskData,
9+
MockPresets,
10+
MockTasks,
711
MockTemplate,
812
MockTemplateVersionExternalAuthGithub,
913
MockTemplateVersionExternalAuthGithubAuthenticated,
1014
MockUserOwner,
11-
MockWorkspace,
12-
MockWorkspaceAppStatus,
1315
mockApiError,
1416
} from "testHelpers/entities";
1517
import {
@@ -31,6 +33,7 @@ const meta: Meta<typeof TasksPage> = {
3133
},
3234
beforeEach: () => {
3335
spyOn(API, "getTemplateVersionExternalAuth").mockResolvedValue([]);
36+
spyOn(API, "getTemplateVersionPresets").mockResolvedValue(null);
3437
spyOn(API, "getUsers").mockResolvedValue({
3538
users: MockUsers,
3639
count: MockUsers.length,
@@ -53,7 +56,7 @@ type Story = StoryObj<typeof TasksPage>;
5356
export const LoadingAITemplates: Story = {
5457
beforeEach: () => {
5558
spyOn(data, "fetchAITemplates").mockImplementation(
56-
() => new Promise((res) => 1000 * 60 * 60),
59+
() => new Promise(() => 1000 * 60 * 60),
5760
);
5861
},
5962
};
@@ -79,7 +82,7 @@ export const LoadingTasks: Story = {
7982
beforeEach: () => {
8083
spyOn(data, "fetchAITemplates").mockResolvedValue([MockTemplate]);
8184
spyOn(data, "fetchTasks").mockImplementation(
82-
() => new Promise((res) => 1000 * 60 * 60),
85+
() => new Promise(() => 1000 * 60 * 60),
8386
);
8487
},
8588
play: async ({ canvasElement, step }) => {
@@ -119,15 +122,77 @@ export const LoadedTasks: Story = {
119122
},
120123
};
121124

122-
const newTaskData = {
123-
prompt: "Create a new task",
124-
workspace: {
125-
...MockWorkspace,
126-
id: "workspace-4",
127-
latest_app_status: {
128-
...MockWorkspaceAppStatus,
129-
message: "Task created successfully!",
130-
},
125+
export const LoadedTasksWithPresets: Story = {
126+
decorators: [withProxyProvider()],
127+
beforeEach: () => {
128+
const mockTemplateWithPresets = {
129+
...MockTemplate,
130+
id: "test-template-2",
131+
name: "template-with-presets",
132+
display_name: "Template with Presets",
133+
};
134+
135+
spyOn(data, "fetchAITemplates").mockResolvedValue([
136+
MockTemplate,
137+
mockTemplateWithPresets,
138+
]);
139+
spyOn(data, "fetchTasks").mockResolvedValue(MockTasks);
140+
spyOn(API, "getTemplateVersionPresets").mockImplementation(
141+
async (versionId) => {
142+
// Return presets only for the second template
143+
if (versionId === mockTemplateWithPresets.active_version_id) {
144+
return MockPresets;
145+
}
146+
return null;
147+
},
148+
);
149+
},
150+
};
151+
152+
export const LoadedTasksWithAIPromptPresets: Story = {
153+
decorators: [withProxyProvider()],
154+
beforeEach: () => {
155+
const mockTemplateWithPresets = {
156+
...MockTemplate,
157+
id: "test-template-2",
158+
name: "template-with-presets",
159+
display_name: "Template with AI Prompt Presets",
160+
};
161+
162+
spyOn(data, "fetchAITemplates").mockResolvedValue([
163+
MockTemplate,
164+
mockTemplateWithPresets,
165+
]);
166+
spyOn(data, "fetchTasks").mockResolvedValue(MockTasks);
167+
spyOn(API, "getTemplateVersionPresets").mockImplementation(
168+
async (versionId) => {
169+
// Return presets only for the second template
170+
if (versionId === mockTemplateWithPresets.active_version_id) {
171+
return MockAIPromptPresets;
172+
}
173+
return null;
174+
},
175+
);
176+
},
177+
};
178+
179+
export const LoadedTasksEdgeCases: Story = {
180+
decorators: [withProxyProvider()],
181+
beforeEach: () => {
182+
spyOn(data, "fetchAITemplates").mockResolvedValue([MockTemplate]);
183+
spyOn(data, "fetchTasks").mockResolvedValue(MockTasks);
184+
185+
// Test various edge cases for presets
186+
spyOn(API, "getTemplateVersionPresets").mockImplementation(async () => {
187+
return [
188+
{
189+
ID: "malformed",
190+
Name: "Malformed Preset",
191+
Default: true,
192+
},
193+
// biome-ignore lint/suspicious/noExplicitAny: Testing malformed data edge cases
194+
] as any;
195+
});
131196
},
132197
};
133198

@@ -154,15 +219,15 @@ export const CreateTaskSuccessfully: Story = {
154219
spyOn(data, "fetchAITemplates").mockResolvedValue([MockTemplate]);
155220
spyOn(data, "fetchTasks")
156221
.mockResolvedValueOnce(MockTasks)
157-
.mockResolvedValue([newTaskData, ...MockTasks]);
158-
spyOn(data, "createTask").mockResolvedValue(newTaskData);
222+
.mockResolvedValue([MockNewTaskData, ...MockTasks]);
223+
spyOn(data, "createTask").mockResolvedValue(MockNewTaskData);
159224
},
160225
play: async ({ canvasElement, step }) => {
161226
const canvas = within(canvasElement);
162227

163228
await step("Run task", async () => {
164229
const prompt = await canvas.findByLabelText(/prompt/i);
165-
await userEvent.type(prompt, newTaskData.prompt);
230+
await userEvent.type(prompt, MockNewTaskData.prompt);
166231
const submitButton = canvas.getByRole("button", { name: /run task/i });
167232
await waitFor(() => expect(submitButton).toBeEnabled());
168233
await userEvent.click(submitButton);
@@ -208,8 +273,8 @@ export const WithAuthenticatedExternalAuth: Story = {
208273
beforeEach: () => {
209274
spyOn(data, "fetchTasks")
210275
.mockResolvedValueOnce(MockTasks)
211-
.mockResolvedValue([newTaskData, ...MockTasks]);
212-
spyOn(data, "createTask").mockResolvedValue(newTaskData);
276+
.mockResolvedValue([MockNewTaskData, ...MockTasks]);
277+
spyOn(data, "createTask").mockResolvedValue(MockNewTaskData);
213278
spyOn(API, "getTemplateVersionExternalAuth").mockResolvedValue([
214279
MockTemplateVersionExternalAuthGithubAuthenticated,
215280
]);
@@ -235,8 +300,8 @@ export const MissingExternalAuth: Story = {
235300
beforeEach: () => {
236301
spyOn(data, "fetchTasks")
237302
.mockResolvedValueOnce(MockTasks)
238-
.mockResolvedValue([newTaskData, ...MockTasks]);
239-
spyOn(data, "createTask").mockResolvedValue(newTaskData);
303+
.mockResolvedValue([MockNewTaskData, ...MockTasks]);
304+
spyOn(data, "createTask").mockResolvedValue(MockNewTaskData);
240305
spyOn(API, "getTemplateVersionExternalAuth").mockResolvedValue([
241306
MockTemplateVersionExternalAuthGithub,
242307
]);
@@ -246,7 +311,7 @@ export const MissingExternalAuth: Story = {
246311

247312
await step("Submit is disabled", async () => {
248313
const prompt = await canvas.findByLabelText(/prompt/i);
249-
await userEvent.type(prompt, newTaskData.prompt);
314+
await userEvent.type(prompt, MockNewTaskData.prompt);
250315
const submitButton = canvas.getByRole("button", { name: /run task/i });
251316
expect(submitButton).toBeDisabled();
252317
});
@@ -262,8 +327,8 @@ export const ExternalAuthError: Story = {
262327
beforeEach: () => {
263328
spyOn(data, "fetchTasks")
264329
.mockResolvedValueOnce(MockTasks)
265-
.mockResolvedValue([newTaskData, ...MockTasks]);
266-
spyOn(data, "createTask").mockResolvedValue(newTaskData);
330+
.mockResolvedValue([MockNewTaskData, ...MockTasks]);
331+
spyOn(data, "createTask").mockResolvedValue(MockNewTaskData);
267332
spyOn(API, "getTemplateVersionExternalAuth").mockRejectedValue(
268333
mockApiError({
269334
message: "Failed to load external auth",
@@ -275,7 +340,7 @@ export const ExternalAuthError: Story = {
275340

276341
await step("Submit is disabled", async () => {
277342
const prompt = await canvas.findByLabelText(/prompt/i);
278-
await userEvent.type(prompt, newTaskData.prompt);
343+
await userEvent.type(prompt, MockNewTaskData.prompt);
279344
const submitButton = canvas.getByRole("button", { name: /run task/i });
280345
expect(submitButton).toBeDisabled();
281346
});
@@ -308,35 +373,3 @@ export const NonAdmin: Story = {
308373
});
309374
},
310375
};
311-
312-
const MockTasks = [
313-
{
314-
workspace: {
315-
...MockWorkspace,
316-
latest_app_status: MockWorkspaceAppStatus,
317-
},
318-
prompt: "Create competitors page",
319-
},
320-
{
321-
workspace: {
322-
...MockWorkspace,
323-
id: "workspace-2",
324-
latest_app_status: {
325-
...MockWorkspaceAppStatus,
326-
message: "Avatar size fixed!",
327-
},
328-
},
329-
prompt: "Fix user avatar size",
330-
},
331-
{
332-
workspace: {
333-
...MockWorkspace,
334-
id: "workspace-3",
335-
latest_app_status: {
336-
...MockWorkspaceAppStatus,
337-
message: "Accessibility issues fixed!",
338-
},
339-
},
340-
prompt: "Fix accessibility issues",
341-
},
342-
];

0 commit comments

Comments
 (0)