Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandredev3 committed May 18, 2024
2 parents e5cbf03 + 14cffd4 commit cf81919
Show file tree
Hide file tree
Showing 82 changed files with 729 additions and 295 deletions.
2 changes: 2 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@
"tender-moose-tell",
"tender-oranges-rhyme",
"thin-parents-heal",
"thirty-islands-kiss",
"tidy-balloons-suffer",
"tidy-dryers-sleep",
"tidy-tomatoes-explain",
"tiny-doors-type",
"tiny-elephants-scream",
"tricky-bulldogs-heal",
Expand Down
37 changes: 37 additions & 0 deletions apps/webapp/app/components/environments/EnvironmentLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@ const variants = {
large: "h-6 text-xs px-1.5 rounded",
};

export function EnvironmentTypeLabel({
environment,
size = "small",
className,
}: {
environment: Environment;
size?: keyof typeof variants;
className?: string;
}) {
return (
<span
className={cn(
"text-midnight-900 inline-flex items-center justify-center whitespace-nowrap border font-medium uppercase tracking-wider",
environmentBorderClassName(environment),
environmentTextClassName(environment),
variants[size],
className
)}
>
{environmentTypeTitle(environment)}
</span>
);
}

export function EnvironmentLabel({
environment,
size = "small",
Expand Down Expand Up @@ -116,6 +140,19 @@ export function environmentTitle(environment: Environment, username?: string) {
}
}

export function environmentTypeTitle(environment: Environment) {
switch (environment.type) {
case "PRODUCTION":
return "Prod";
case "STAGING":
return "Staging";
case "DEVELOPMENT":
return "Dev";
case "PREVIEW":
return "Preview";
}
}

export function environmentColorClassName(environment: Environment) {
switch (environment.type) {
case "PRODUCTION":
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/app/components/primitives/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background-dimmed px-4 pb-4 pt-3.5 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
"fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background-dimmed px-4 pb-4 pt-2.5 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
className
)}
{...props}
Expand Down Expand Up @@ -74,7 +74,7 @@ DialogContent.displayName = DialogPrimitive.Content.displayName;

const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("flex flex-col text-left font-medium text-text-dimmed", className)}
className={cn("flex flex-col text-left font-medium text-text-bright", className)}
{...props}
/>
);
Expand Down
19 changes: 16 additions & 3 deletions apps/webapp/app/components/runs/v3/EnabledStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import { BoltSlashIcon, CheckCircleIcon } from "@heroicons/react/20/solid";

export function EnabledStatus({ enabled }: { enabled: boolean }) {
type EnabledStatusProps = {
enabled: boolean;
enabledIcon?: React.ComponentType<any>;
disabledIcon?: React.ComponentType<any>;
};

export function EnabledStatus({
enabled,
enabledIcon = CheckCircleIcon,
disabledIcon = BoltSlashIcon,
}: EnabledStatusProps) {
const EnabledIcon = enabledIcon;
const DisabledIcon = disabledIcon;

switch (enabled) {
case true:
return (
<div className="flex items-center gap-1 text-xs text-success">
<CheckCircleIcon className="h-4 w-4" />
<EnabledIcon className="size-4" />
Enabled
</div>
);
case false:
return (
<div className="text-dimmed flex items-center gap-1 text-xs">
<BoltSlashIcon className="h-4 w-4" />
<DisabledIcon className="size-4" />
Disabled
</div>
);
Expand Down
11 changes: 9 additions & 2 deletions apps/webapp/app/models/orgIntegration.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type SlackSecret = z.infer<typeof SlackSecretSchema>;

const REDIRECT_AFTER_AUTH_KEY = "redirect-back-after-auth";

type OrganizationIntegrationForService<TService extends IntegrationService> = Omit<
export type OrganizationIntegrationForService<TService extends IntegrationService> = Omit<
AuthenticatableIntegration,
"service"
> & {
Expand Down Expand Up @@ -83,7 +83,14 @@ export class OrgIntegrationRepository {

static slackAuthorizationUrl(
state: string,
scopes: string[] = ["channels:read", "groups:read", "im:read", "mpim:read", "chat:write"],
scopes: string[] = [
"channels:read",
"groups:read",
"im:read",
"mpim:read",
"chat:write",
"chat:write.public",
],
userScopes: string[] = ["channels:read", "groups:read", "im:read", "mpim:read", "chat:write"]
) {
return `https://slack.com/oauth/v2/authorize?client_id=${
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const ApiAlertType = z.enum(["attempt_failure", "deployment_failure", "de

export type ApiAlertType = z.infer<typeof ApiAlertType>;

export const ApiAlertEnvironmentType = z.enum(["STAGING", "PRODUCTION"]);

export type ApiAlertEnvironmentType = z.infer<typeof ApiAlertEnvironmentType>;

export const ApiAlertChannel = z.enum(["email", "webhook"]);

export type ApiAlertChannel = z.infer<typeof ApiAlertChannel>;
Expand All @@ -34,6 +38,7 @@ export const ApiCreateAlertChannel = z.object({
channel: ApiAlertChannel,
channelData: ApiAlertChannelData,
deduplicationKey: z.string().optional(),
environmentTypes: ApiAlertEnvironmentType.array().default(["STAGING", "PRODUCTION"]),
});

export type ApiCreateAlertChannel = z.infer<typeof ApiCreateAlertChannel>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { InlineCode } from "~/components/code/InlineCode";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { Callout, variantClasses } from "~/components/primitives/Callout";
import { Checkbox } from "~/components/primitives/Checkbox";
import { Dialog, DialogContent, DialogHeader } from "~/components/primitives/Dialog";
import { Fieldset } from "~/components/primitives/Fieldset";
Expand All @@ -20,14 +20,17 @@ import { Hint } from "~/components/primitives/Hint";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { Paragraph } from "~/components/primitives/Paragraph";
import SegmentedControl from "~/components/primitives/SegmentedControl";
import { Select, SelectItem } from "~/components/primitives/Select";
import { InfoIconTooltip } from "~/components/primitives/Tooltip";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { redirectWithSuccessMessage } from "~/models/message.server";
import { findProjectBySlug } from "~/models/project.server";
import { NewAlertChannelPresenter } from "~/presenters/v3/NewAlertChannelPresenter.server";
import { requireUserId } from "~/services/session.server";
import { cn } from "~/utils/cn";
import { ProjectParamSchema, v3ProjectAlertsPath } from "~/utils/pathBuilder";
import {
CreateAlertChannelOptions,
Expand All @@ -40,6 +43,10 @@ const FormSchema = z
.array(z.enum(["TASK_RUN_ATTEMPT", "DEPLOYMENT_FAILURE", "DEPLOYMENT_SUCCESS"]))
.min(1)
.or(z.enum(["TASK_RUN_ATTEMPT", "DEPLOYMENT_FAILURE", "DEPLOYMENT_SUCCESS"])),
environmentTypes: z
.array(z.enum(["STAGING", "PRODUCTION"]))
.min(1)
.or(z.enum(["STAGING", "PRODUCTION"])),
type: z.enum(["WEBHOOK", "SLACK", "EMAIL"]).default("EMAIL"),
channelValue: z.string().nonempty(),
integrationId: z.string().optional(),
Expand Down Expand Up @@ -81,6 +88,9 @@ function formDataToCreateAlertChannelOptions(
alertTypes: Array.isArray(formData.alertTypes)
? formData.alertTypes
: [formData.alertTypes],
environmentTypes: Array.isArray(formData.environmentTypes)
? formData.environmentTypes
: [formData.environmentTypes],
channel: {
type: "WEBHOOK",
url: formData.channelValue,
Expand All @@ -93,6 +103,9 @@ function formDataToCreateAlertChannelOptions(
alertTypes: Array.isArray(formData.alertTypes)
? formData.alertTypes
: [formData.alertTypes],
environmentTypes: Array.isArray(formData.environmentTypes)
? formData.environmentTypes
: [formData.environmentTypes],
channel: {
type: "EMAIL",
email: formData.channelValue,
Expand All @@ -107,6 +120,9 @@ function formDataToCreateAlertChannelOptions(
alertTypes: Array.isArray(formData.alertTypes)
? formData.alertTypes
: [formData.alertTypes],
environmentTypes: Array.isArray(formData.environmentTypes)
? formData.environmentTypes
: [formData.environmentTypes],
channel: {
type: "SLACK",
channelId,
Expand Down Expand Up @@ -193,20 +209,27 @@ export default function Page() {
const project = useProject();
const [currentAlertChannel, setCurrentAlertChannel] = useState<string | null>(option ?? "EMAIL");

const [selectedSlackChannelValue, setSelectedSlackChannelValue] = useState<string | undefined>();

const selectedSlackChannel = slack.channels?.find(
(s) => selectedSlackChannelValue === `${s.id}/${s.name}`
);

const isLoading =
navigation.state !== "idle" &&
navigation.formMethod === "post" &&
navigation.formData?.get("action") === "create";

const [form, { channelValue: channelValue, alertTypes, type, integrationId }] = useForm({
id: "create-alert",
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema: FormSchema });
},
shouldRevalidate: "onSubmit",
});
const [form, { channelValue: channelValue, alertTypes, environmentTypes, type, integrationId }] =
useForm({
id: "create-alert",
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema: FormSchema });
},
shouldRevalidate: "onSubmit",
});

useEffect(() => {
setIsOpen(true);
Expand Down Expand Up @@ -271,6 +294,9 @@ export default function Page() {
dropdownIcon
variant="tertiary/medium"
items={slack.channels}
setValue={(value) => {
typeof value === "string" && setSelectedSlackChannelValue(value);
}}
filter={(channel, search) =>
channel.name?.toLowerCase().includes(search.toLowerCase()) ?? false
}
Expand All @@ -290,10 +316,19 @@ export default function Page() {
</>
)}
</Select>
<Hint className="leading-relaxed">
If selecting a private channel, you will need to invite the bot to the channel
using <InlineCode variant="extra-small">/invite @Trigger.dev</InlineCode>
</Hint>
{selectedSlackChannel && selectedSlackChannel.is_private && (
<Callout
variant="warning"
className={cn("text-sm", variantClasses.warning.textColor)}
>
To receive alerts in the{" "}
<InlineCode variant="extra-small">{selectedSlackChannel.name}</InlineCode>{" "}
channel, you need to invite the @Trigger.dev Slack Bot. Go to the channel in
Slack and type:{" "}
<InlineCode variant="extra-small">/invite @Trigger.dev</InlineCode>.
</Callout>
)}

<FormError id={channelValue.errorId}>{channelValue.error}</FormError>
<input type="hidden" name="integrationId" value={slack.integrationId} />
</>
Expand Down Expand Up @@ -324,24 +359,28 @@ export default function Page() {
</InputGroup>
)}

<InputGroup fullWidth>
<Label>Events</Label>

<Checkbox
name={alertTypes.name}
id="TASK_RUN_ATTEMPT"
value="TASK_RUN_ATTEMPT"
variant="simple/small"
label="Task run failure"
defaultChecked
/>
<InputGroup>
<Label>Alert me when</Label>

<div className="flex items-center gap-1">
<Checkbox
name={alertTypes.name}
id="TASK_RUN_ATTEMPT"
value="TASK_RUN_ATTEMPT"
variant="simple/small"
label="Task run attempts fail"
defaultChecked
className="pr-0"
/>
<InfoIconTooltip content="You'll receive an alert every time an attempt fails on a run." />
</div>

<Checkbox
name={alertTypes.name}
id="DEPLOYMENT_FAILURE"
value="DEPLOYMENT_FAILURE"
variant="simple/small"
label="Deployment failure"
label="Deployments fail"
defaultChecked
/>

Expand All @@ -350,13 +389,33 @@ export default function Page() {
id="DEPLOYMENT_SUCCESS"
value="DEPLOYMENT_SUCCESS"
variant="simple/small"
label="Deployment success"
label="Deployments succeed"
defaultChecked
/>

<FormError id={alertTypes.errorId}>{alertTypes.error}</FormError>
</InputGroup>
<InputGroup>
<Label>Environments</Label>
<Checkbox
name={environmentTypes.name}
id="PRODUCTION"
value="PRODUCTION"
variant="simple/small"
label="PROD"
defaultChecked
/>
<Checkbox
name={environmentTypes.name}
id="STAGING"
value="STAGING"
variant="simple/small"
label="STAGING"
defaultChecked
/>

<FormError id={environmentTypes.errorId}>{environmentTypes.error}</FormError>
</InputGroup>
<FormError>{form.error}</FormError>
<div className="border-t border-grid-bright pt-3">
<FormButtons
Expand Down
Loading

0 comments on commit cf81919

Please sign in to comment.