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

Commit8f021d0

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 parent06f4e16 commit8f021d0

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";
@@ -40,6 +44,7 @@ import { Link as RouterLink } from "react-router-dom";
4044
importTextareaAutosizefrom"react-textarea-autosize";
4145
import{pageTitle}from"utils/page";
4246
import{relativeTime}from"utils/time";
47+
import{ExternalAuthButton}from"../CreateWorkspacePage/ExternalAuthButton";
4348
import{typeUserOption,UsersCombobox}from"./UsersCombobox";
4449

4550
typeTasksFilter={
@@ -161,6 +166,21 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
161166
const{ user}=useAuthenticated();
162167
constqueryClient=useQueryClient();
163168

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

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

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

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp