Files
triggerdotdev--trigger.dev/apps/webapp/app/models/organization.server.ts
James Ritchie 708ebbde14 Features: Disabling and deleting jobs (#381)
* Don’t show the Ready To Run Job prompt if you have an Integration that needs attention

* Don’t highlight the row red or green

* Improvements to the contrast of the app sections and dividers

* Removed the Jobs page title as it’s duped info

* using the new border variable

* Sticky last table cell

* Improved the sticky last table cell

* Make any last cell in a table sticky by adding isSticky to it

* Added a dropdown menu to the menu table cell

* table rows can be marked as disabled by adding disabled

* Using the jobTestPath function for the test path

* tidy up imports

* Removed un-used props

* Removed the green badge variant

* Added a new status badge to the Runs table

* Clicking the gradient clicks the row

* Delete Job triggers a modal popup

* Added a large danger button type

* Added some modal styling and started adding data

* Added more styling and data to the delete job modal

* A table can now be given a full width prop

* Large danger button added to Storybook

* Danger button disabled state looks disabled now

* New active badge component to display in the table and logic for showing the env data

* Style updates to the dialog component

* active and job status badges can now have a small size

* Added a new named icon

* The Job page shows the Job status in the PageInfoRow

* Small badge style update

* Runs table has a sticky right cell

* Created a JobStatusTable component

* Added some placeholder help panel content for disabling a Job

* WIP creating a Settings page

* Added a delete button that triggers the delete modal – just need data hooking up

* Implemented deleting jobs from the dashboard

---------

Co-authored-by: Eric Allam <eallam@icloud.com>
2023-08-24 10:30:00 +01:00

182 lines
3.9 KiB
TypeScript

import type {
Organization,
OrgMember,
Project,
RuntimeEnvironment,
User,
} from "@trigger.dev/database";
import { customAlphabet } from "nanoid";
import slug from "slug";
import { prisma, PrismaClientOrTransaction } from "~/db.server";
import { workerQueue } from "~/services/worker.server";
import { createProject } from "./project.server";
export type { Organization };
const nanoid = customAlphabet("1234567890abcdef", 4);
const apiKeyId = customAlphabet(
"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
12
);
export function getOrganizationFromSlug({
userId,
slug,
}: Pick<Organization, "slug"> & {
userId: User["id"];
}) {
return prisma.organization.findFirst({
include: {
environments: true,
},
where: { slug, members: { some: { userId } } },
});
}
export function getOrganizations({ userId }: { userId: User["id"] }) {
return prisma.organization.findMany({
where: { members: { some: { userId } } },
orderBy: { createdAt: "desc" },
include: {
environments: {
orderBy: { slug: "asc" },
},
projects: {
orderBy: { name: "asc" },
include: {
_count: {
select: {
jobs: {
where: {
internal: false,
deletedAt: null,
},
},
},
},
},
},
_count: {
select: {
members: true,
},
},
},
});
}
export async function createOrganization(
{
title,
userId,
projectName,
}: Pick<Organization, "title"> & {
userId: User["id"];
projectName: string;
},
attemptCount = 0
): Promise<Organization & { projects: Project[] }> {
const uniqueOrgSlug = `${slug(title)}-${nanoid(4)}`;
const orgWithSameSlug = await prisma.organization.findFirst({
where: { slug: uniqueOrgSlug },
});
if (attemptCount > 100) {
throw new Error(`Unable to create organization with slug ${uniqueOrgSlug} after 100 attempts`);
}
if (orgWithSameSlug) {
return createOrganization(
{
title,
userId,
projectName,
},
attemptCount + 1
);
}
const organization = await prisma.organization.create({
data: {
title,
slug: uniqueOrgSlug,
members: {
create: {
userId: userId,
role: "ADMIN",
},
},
},
include: {
members: true,
},
});
const project = await createProject({
organizationSlug: organization.slug,
name: projectName,
userId,
});
return { ...organization, projects: [project] };
}
export async function createEnvironment(
organization: Organization,
project: Project,
type: RuntimeEnvironment["type"],
member?: OrgMember,
prismaClient: PrismaClientOrTransaction = prisma
) {
const slug = envSlug(type);
const apiKey = createApiKeyForEnv(type);
const pkApiKey = createPkApiKeyForEnv(type);
return await prismaClient.runtimeEnvironment.create({
data: {
slug,
apiKey,
pkApiKey,
autoEnableInternalSources: type !== "DEVELOPMENT",
organization: {
connect: {
id: organization.id,
},
},
project: {
connect: {
id: project.id,
},
},
orgMember: member ? { connect: { id: member.id } } : undefined,
type,
},
});
}
function createApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `tr_${envSlug(envType)}_${apiKeyId(20)}`;
}
function createPkApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `pk_${envSlug(envType)}_${apiKeyId(20)}`;
}
function envSlug(environmentType: RuntimeEnvironment["type"]) {
switch (environmentType) {
case "DEVELOPMENT": {
return "dev";
}
case "PRODUCTION": {
return "prod";
}
case "STAGING": {
return "staging";
}
case "PREVIEW": {
return "preview";
}
}
}