Skip to content

Commit

Permalink
V3 Test feature (#915)
Browse files Browse the repository at this point in the history
* Added isTest to TaskRun

* TriggerTaskService now accepts isTest and adds it to TaskRun if set

* Set the cliVersion and sdkVersion when indexing

* Added a test page

* Radio buttons are now circles and accept a ReactNode

* The background of the code editor is now darker

* WIP on the test task page

* Improvements to the test page

* Testing is working

* Fix typo

* Show the real isTest value in the runs table

* Added a key to the outlet so it correctly resets all the state on the sub-route
  • Loading branch information
matt-aitken authored Feb 28, 2024
1 parent 1fabcf6 commit 479445f
Show file tree
Hide file tree
Showing 17 changed files with 704 additions and 9 deletions.
2 changes: 1 addition & 1 deletion apps/webapp/app/components/code/codeMirrorTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function darkTheme(): Extension {
violet = "#c678dd",
darkBackground = "#21252b",
highlightBackground = "rgba(71,85,105,0.2)",
background = "rgba(11, 16, 24 ,100)",
background = "#121317",
tooltipBackground = "#353a42",
selection = "rgb(71 85 105)",
cursor = "#528bff",
Expand Down
9 changes: 9 additions & 0 deletions apps/webapp/app/components/navigation/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
AcademicCapIcon,
ArrowRightIcon,
ArrowRightOnRectangleIcon,
BeakerIcon,
ChartBarIcon,
CursorArrowRaysIcon,
IdentificationIcon,
Expand Down Expand Up @@ -42,6 +43,7 @@ import {
v3EnvironmentVariablesPath,
v3ProjectPath,
v3RunsPath,
v3TestPath,
} from "~/utils/pathBuilder";
import { Feedback } from "../Feedback";
import { ImpersonationBanner } from "../ImpersonationBanner";
Expand Down Expand Up @@ -510,6 +512,13 @@ function V3ProjectSideMenu({
iconColor="text-teal-500"
to={v3RunsPath(organization, project)}
/>
<SideMenuItem
name="Test"
icon={BeakerIcon}
iconColor="text-lime-500"
to={v3TestPath(organization, project)}
data-action="test"
/>
<SideMenuItem
name="API Keys"
icon={KeyIcon}
Expand Down
40 changes: 37 additions & 3 deletions apps/webapp/app/components/primitives/RadioButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,40 @@ const variants = {
},
};

type RadioButtonCircleProps = {
checked: boolean;
boxClassName?: string;
outerCircleClassName?: string;
innerCircleClassName?: string;
};

export function RadioButtonCircle({
checked,
boxClassName,
outerCircleClassName,
innerCircleClassName,
}: RadioButtonCircleProps) {
return (
<div
className={cn(
"aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-full border border-slate-700 ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
boxClassName
)}
>
{checked && (
<div
className={cn(
"flex h-full w-full items-center justify-center bg-indigo-700",
outerCircleClassName
)}
>
<Circle className={cn("h-1.5 w-1.5 fill-white text-white", innerCircleClassName)} />
</div>
)}
</div>
);
}

export const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
Expand All @@ -66,8 +100,8 @@ type RadioGroupItemProps = Omit<
"onChange"
> & {
variant?: keyof typeof variants;
label?: string;
description?: string;
label?: React.ReactNode;
description?: React.ReactNode;
badges?: string[];
className?: string;
icon?: React.ReactNode;
Expand Down Expand Up @@ -95,7 +129,7 @@ export const RadioGroupItem = React.forwardRef<
>
<div
className={cn(
"aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-sm border border-slate-700 ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-full border border-slate-700 ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
variation.inputPosition
)}
>
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/runs/v3/TaskRunStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ExtendedTaskAttemptStatus } from "./RunFilters";

export function TaskRunStatus({ status }: { status: ExtendedTaskAttemptStatus | null }) {
return (
<span className="flex items-center gap-1">
<span className="inline-flex items-center gap-1">
<TaskRunStatusIcon status={status} className="h-4 w-4" />
<TaskRunStatusLabel status={status} />
</span>
Expand Down
10 changes: 10 additions & 0 deletions apps/webapp/app/components/stories/RadioGroup.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ function RadioGroupExample() {
variant="icon"
icon={<NamedIcon name="tree" className="h-8 w-8" />}
/>
<RadioGroupItem
id="r8"
label={
<div className="flex items-center gap-2">
<span>This is a</span> <span className="text-red-500">React node</span>
</div>
}
value={"8"}
variant="simple/small"
/>
</RadioGroup>
</form>
</div>
Expand Down
20 changes: 20 additions & 0 deletions apps/webapp/app/models/runtimeEnvironment.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,23 @@ export async function findEnvironmentByPublicApiKey(apiKey: string) {

return environment;
}

export async function findEnvironmentById(id: string) {
const environment = await prisma.runtimeEnvironment.findUnique({
where: {
id,
},
include: {
project: true,
organization: true,
orgMember: true,
},
});

//don't return deleted projects
if (environment?.project.deletedAt !== null) {
return null;
}

return environment;
}
4 changes: 3 additions & 1 deletion apps/webapp/app/presenters/v3/RunListPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export class RunListPresenter {
createdAt: Date;
startedAt: Date | null;
completedAt: Date | null;
isTest: boolean;
attempts: BigInt;
}[]
>`
Expand All @@ -112,6 +113,7 @@ export class RunListPresenter {
tr."createdAt" AS "createdAt",
tra."startedAt" AS "startedAt",
tra."completedAt" AS "completedAt",
tr."isTest" AS "isTest",
COUNT(tra.id) AS attempts
FROM
"TaskRun" tr
Expand Down Expand Up @@ -211,7 +213,7 @@ export class RunListPresenter {
createdAt: run.createdAt,
startedAt: run.startedAt,
completedAt: run.completedAt,
isTest: false,
isTest: run.isTest,
status: run.status,
version: run.version,
taskIdentifier: run.taskIdentifier,
Expand Down
101 changes: 101 additions & 0 deletions apps/webapp/app/presenters/v3/TestPresenter.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { PrismaClient, prisma } from "~/db.server";
import { getUsername } from "~/utils/username";

type TaskListOptions = {
userId: string;
projectSlug: string;
};

export type TaskList = Awaited<ReturnType<TestPresenter["call"]>>;
export type TaskListItem = TaskList["tasks"][0];

export class TestPresenter {
#prismaClient: PrismaClient;

constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}

public async call({ userId, projectSlug }: TaskListOptions) {
// Find the project scoped to the organization
const project = await this.#prismaClient.project.findFirstOrThrow({
select: {
id: true,
environments: {
select: {
id: true,
type: true,
slug: true,
orgMember: {
select: {
user: {
select: {
id: true,
name: true,
displayName: true,
},
},
},
},
},
},
},
where: {
slug: projectSlug,
},
});

//get all possible tasks
const tasks = await this.#prismaClient.$queryRaw<
{
id: string;
version: string;
runtimeEnvironmentId: string;
taskIdentifier: string;
filePath: string;
exportName: string;
friendlyId: string;
}[]
>`
WITH workers AS (
SELECT
bw.*,
ROW_NUMBER() OVER(PARTITION BY bw."runtimeEnvironmentId" ORDER BY bw.version DESC) AS rn
FROM
"BackgroundWorker" bw
WHERE "projectId" = ${project.id}
),
latest_workers AS (SELECT * FROM workers WHERE rn = 1)
SELECT "BackgroundWorkerTask".id, version, "BackgroundWorkerTask"."runtimeEnvironmentId", slug as "taskIdentifier", "filePath", "exportName", "BackgroundWorkerTask"."friendlyId"
FROM latest_workers
JOIN "BackgroundWorkerTask" ON "BackgroundWorkerTask"."workerId" = latest_workers.id;;
`;

return {
tasks: tasks.map((task) => {
const environment = project.environments.find(
(env) => env.id === task.runtimeEnvironmentId
);

if (!environment) {
throw new Error(`Environment not found for Task ${task.id}`);
}

return {
id: task.id,
version: task.version,
taskIdentifier: task.taskIdentifier,
filePath: task.filePath,
exportName: task.exportName,
friendlyId: task.friendlyId,
environment: {
type: environment.type,
slug: environment.slug,
userId: environment.orgMember?.user.id,
userName: getUsername(environment.orgMember?.user),
},
};
}),
};
}
}
Loading

0 comments on commit 479445f

Please sign in to comment.