Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit324838d

Browse files
committed
Check external auth before running task
It seems we do not validate external auth in the backend currently, so Iopted to do this in the frontend to match the create workspace page.
1 parent80d1188 commit324838d

File tree

3 files changed

+176
-12
lines changed

3 files changed

+176
-12
lines changed

‎site/src/hooks/useExternalAuth.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ export const useExternalAuth = (versionId: string | undefined) => {
1212
setExternalAuthPollingState("polling");
1313
},[]);
1414

15-
const{data:externalAuth,isPending:isLoadingExternalAuth}=useQuery({
15+
const{
16+
data:externalAuth,
17+
isPending:isLoadingExternalAuth,
18+
error,
19+
}=useQuery({
1620
...templateVersionExternalAuth(versionId??""),
1721
enabled:!!versionId,
1822
refetchInterval:externalAuthPollingState==="polling" ?1000 :false,
@@ -45,5 +49,6 @@ export const useExternalAuth = (versionId: string | undefined) => {
4549
externalAuth,
4650
externalAuthPollingState,
4751
isLoadingExternalAuth,
52+
externalAuthError:error,
4853
};
4954
};

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

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
importtype{Meta,StoryObj}from"@storybook/react";
2-
import{expect,spyOn,userEvent,within}from"@storybook/test";
2+
import{expect,spyOn,userEvent,waitFor,within}from"@storybook/test";
33
import{API}from"api/api";
44
import{MockUsers}from"pages/UsersPage/storybookData/users";
55
import{
66
MockTemplate,
7+
MockTemplateVersionExternalAuthGithub,
8+
MockTemplateVersionExternalAuthGithubAuthenticated,
79
MockUserOwner,
810
MockWorkspace,
911
MockWorkspaceAppStatus,
@@ -27,10 +29,20 @@ const meta: Meta<typeof TasksPage> = {
2729
},
2830
},
2931
beforeEach:()=>{
32+
spyOn(API,"getTemplateVersionExternalAuth").mockResolvedValue([]);
3033
spyOn(API,"getUsers").mockResolvedValue({
3134
users:MockUsers,
3235
count:MockUsers.length,
3336
});
37+
spyOn(data,"fetchAITemplates").mockResolvedValue([
38+
MockTemplate,
39+
{
40+
...MockTemplate,
41+
id:"test-template-2",
42+
name:"template 2",
43+
display_name:"Template 2",
44+
},
45+
]);
3446
},
3547
};
3648

@@ -134,6 +146,7 @@ export const CreateTaskSuccessfully: Story = {
134146
constprompt=awaitcanvas.findByLabelText(/prompt/i);
135147
awaituserEvent.type(prompt,newTaskData.prompt);
136148
constsubmitButton=canvas.getByRole("button",{name:/runtask/i});
149+
awaitwaitFor(()=>expect(submitButton).toBeEnabled());
137150
awaituserEvent.click(submitButton);
138151
});
139152

@@ -164,6 +177,7 @@ export const CreateTaskError: Story = {
164177
constprompt=awaitcanvas.findByLabelText(/prompt/i);
165178
awaituserEvent.type(prompt,"Create a new task");
166179
constsubmitButton=canvas.getByRole("button",{name:/runtask/i});
180+
awaitwaitFor(()=>expect(submitButton).toBeEnabled());
167181
awaituserEvent.click(submitButton);
168182
});
169183

@@ -173,6 +187,98 @@ export const CreateTaskError: Story = {
173187
},
174188
};
175189

190+
exportconstWithExternalAuth:Story={
191+
decorators:[withProxyProvider()],
192+
beforeEach:()=>{
193+
spyOn(data,"fetchTasks")
194+
.mockResolvedValueOnce(MockTasks)
195+
.mockResolvedValue([newTaskData, ...MockTasks]);
196+
spyOn(data,"createTask").mockResolvedValue(newTaskData);
197+
spyOn(API,"getTemplateVersionExternalAuth").mockResolvedValue([
198+
MockTemplateVersionExternalAuthGithubAuthenticated,
199+
]);
200+
},
201+
play:async({ canvasElement, step})=>{
202+
constcanvas=within(canvasElement);
203+
204+
awaitstep("Run task",async()=>{
205+
constprompt=awaitcanvas.findByLabelText(/prompt/i);
206+
awaituserEvent.type(prompt,newTaskData.prompt);
207+
constsubmitButton=canvas.getByRole("button",{name:/runtask/i});
208+
awaitwaitFor(()=>expect(submitButton).toBeEnabled());
209+
awaituserEvent.click(submitButton);
210+
});
211+
212+
awaitstep("Verify task in the table",async()=>{
213+
awaitcanvas.findByRole("row",{
214+
name:newRegExp(newTaskData.prompt,"i"),
215+
});
216+
});
217+
218+
awaitstep("Does not render external auth",async()=>{
219+
expect(
220+
canvas.queryByText(/externalauthentication/),
221+
).not.toBeInTheDocument();
222+
});
223+
},
224+
};
225+
226+
exportconstMissingExternalAuth:Story={
227+
decorators:[withProxyProvider()],
228+
beforeEach:()=>{
229+
spyOn(data,"fetchTasks")
230+
.mockResolvedValueOnce(MockTasks)
231+
.mockResolvedValue([newTaskData, ...MockTasks]);
232+
spyOn(data,"createTask").mockResolvedValue(newTaskData);
233+
spyOn(API,"getTemplateVersionExternalAuth").mockResolvedValue([
234+
MockTemplateVersionExternalAuthGithub,
235+
]);
236+
},
237+
play:async({ canvasElement, step})=>{
238+
constcanvas=within(canvasElement);
239+
240+
awaitstep("Submit is disabled",async()=>{
241+
constprompt=awaitcanvas.findByLabelText(/prompt/i);
242+
awaituserEvent.type(prompt,newTaskData.prompt);
243+
constsubmitButton=canvas.getByRole("button",{name:/runtask/i});
244+
expect(submitButton).toBeDisabled();
245+
});
246+
247+
awaitstep("Renders external authentication",async()=>{
248+
awaitcanvas.findByRole("button",{name:/loginwithgithub/i});
249+
});
250+
},
251+
};
252+
253+
exportconstExternalAuthError:Story={
254+
decorators:[withProxyProvider()],
255+
beforeEach:()=>{
256+
spyOn(data,"fetchTasks")
257+
.mockResolvedValueOnce(MockTasks)
258+
.mockResolvedValue([newTaskData, ...MockTasks]);
259+
spyOn(data,"createTask").mockResolvedValue(newTaskData);
260+
spyOn(API,"getTemplateVersionExternalAuth").mockRejectedValue(
261+
mockApiError({
262+
message:"Failed to load external auth",
263+
}),
264+
);
265+
},
266+
play:async({ canvasElement, step})=>{
267+
constcanvas=within(canvasElement);
268+
269+
awaitstep("Submit is disabled",async()=>{
270+
constprompt=awaitcanvas.findByLabelText(/prompt/i);
271+
awaituserEvent.type(prompt,newTaskData.prompt);
272+
constsubmitButton=canvas.getByRole("button",{name:/runtask/i});
273+
expect(submitButton).toBeDisabled();
274+
});
275+
276+
awaitstep("Renders error",async()=>{
277+
awaitcanvas.findByText(/failedtoloadexternalauth/i);
278+
});
279+
},
280+
};
281+
176282
exportconstNonAdmin:Story={
177283
decorators:[withProxyProvider()],
178284
parameters:{

‎site/src/pages/TasksPage/TasksPage.tsx

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { API } from "api/api";
22
import{getErrorDetail,getErrorMessage}from"api/errors";
33
import{disabledRefetchOptions}from"api/queries/util";
44
importtype{Template}from"api/typesGenerated";
5+
import{ErrorAlert}from"components/Alert/ErrorAlert";
56
import{Avatar}from"components/Avatar/Avatar";
67
import{AvatarData}from"components/Avatar/AvatarData";
78
import{Button}from"components/Button/Button";
9+
import{Form,FormFields,FormSection}from"components/Form/Form";
810
import{displayError}from"components/GlobalSnackbar/utils";
911
import{Margins}from"components/Margins/Margins";
1012
import{
@@ -28,7 +30,9 @@ import {
2830
TableHeader,
2931
TableRow,
3032
}from"components/Table/Table";
33+
3134
import{useAuthenticated}from"hooks";
35+
import{useExternalAuth}from"hooks/useExternalAuth";
3236
import{ExternalLinkIcon,RotateCcwIcon,SendIcon}from"lucide-react";
3337
import{AI_PROMPT_PARAMETER_NAME,typeTask}from"modules/tasks/tasks";
3438
import{WorkspaceAppStatus}from"modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus";
@@ -39,6 +43,7 @@ import { Link as RouterLink } from "react-router-dom";
3943
importTextareaAutosizefrom"react-textarea-autosize";
4044
import{pageTitle}from"utils/page";
4145
import{relativeTime}from"utils/time";
46+
import{ExternalAuthButton}from"../CreateWorkspacePage/ExternalAuthButton";
4247
import{typeUserOption,UsersCombobox}from"./UsersCombobox";
4348

4449
typeTasksFilter={
@@ -160,6 +165,21 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
160165
const{ user}=useAuthenticated();
161166
constqueryClient=useQueryClient();
162167

168+
const[templateId,setTemplateId]=useState<string>(templates[0].id);
169+
const{
170+
externalAuth,
171+
externalAuthPollingState,
172+
startPollingExternalAuth,
173+
isLoadingExternalAuth,
174+
externalAuthError,
175+
}=useExternalAuth(
176+
templates.find((t)=>t.id===templateId)?.active_version_id,
177+
);
178+
179+
consthasAllRequiredExternalAuth=externalAuth?.every(
180+
(auth)=>auth.optional||auth.authenticated,
181+
);
182+
163183
constcreateTaskMutation=useMutation({
164184
mutationFn:async({ prompt, templateId}:CreateTaskMutationFnProps)=>
165185
data.createTask(prompt,user.id,templateId),
@@ -196,12 +216,13 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
196216
};
197217

198218
return(
199-
<form
200-
className="border border-border border-solid rounded-lg p-4"
201-
onSubmit={onSubmit}
202-
aria-label="Create AI task"
203-
>
204-
<fieldsetdisabled={createTaskMutation.isPending}>
219+
<FormonSubmit={onSubmit}aria-label="Create AI task">
220+
{Boolean(externalAuthError)&&<ErrorAlerterror={externalAuthError}/>}
221+
222+
<fieldset
223+
className="border border-border border-solid rounded-lg p-4"
224+
disabled={createTaskMutation.isPending}
225+
>
205226
<labelhtmlFor="prompt"className="sr-only">
206227
Prompt
207228
</label>
@@ -214,7 +235,12 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
214235
text-sm shadow-sm text-content-primary placeholder:text-content-secondary md:text-sm`}
215236
/>
216237
<divclassName="flex items-center justify-between pt-2">
217-
<Selectname="templateID"defaultValue={templates[0].id}required>
238+
<Select
239+
name="templateID"
240+
onValueChange={(value)=>setTemplateId(value)}
241+
defaultValue={templates[0].id}
242+
required
243+
>
218244
<SelectTriggerclassName="w-52 text-xs [&_svg]:size-icon-xs border-0 bg-surface-secondary h-8 px-3">
219245
<SelectValueplaceholder="Select a template"/>
220246
</SelectTrigger>
@@ -231,15 +257,42 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
231257
</SelectContent>
232258
</Select>
233259

234-
<Buttonsize="sm"type="submit">
235-
<Spinnerloading={createTaskMutation.isPending}>
260+
<Button
261+
size="sm"
262+
type="submit"
263+
disabled={!hasAllRequiredExternalAuth}
264+
>
265+
<Spinner
266+
loading={createTaskMutation.isPending||isLoadingExternalAuth}
267+
>
236268
<SendIcon/>
237269
</Spinner>
238270
Run task
239271
</Button>
240272
</div>
241273
</fieldset>
242-
</form>
274+
275+
{!hasAllRequiredExternalAuth&&
276+
externalAuth&&
277+
externalAuth.length>0&&(
278+
<FormSection
279+
title="External Authentication"
280+
description="This template uses external services for authentication."
281+
>
282+
<FormFields>
283+
{externalAuth.map((auth)=>(
284+
<ExternalAuthButton
285+
key={auth.id}
286+
auth={auth}
287+
isLoading={externalAuthPollingState==="polling"}
288+
onStartPolling={startPollingExternalAuth}
289+
displayRetry={externalAuthPollingState==="abandoned"}
290+
/>
291+
))}
292+
</FormFields>
293+
</FormSection>
294+
)}
295+
</Form>
243296
);
244297
};
245298

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp