diff --git a/apps/webapp/app/components/integrations/WorkflowConnections.tsx b/apps/webapp/app/components/integrations/WorkflowConnections.tsx
index 7d54aad0f40..b714ac915c6 100644
--- a/apps/webapp/app/components/integrations/WorkflowConnections.tsx
+++ b/apps/webapp/app/components/integrations/WorkflowConnections.tsx
@@ -1,69 +1,40 @@
+import { Link } from "@remix-run/react";
+import classNames from "classnames";
import invariant from "tiny-invariant";
-import { useConnectionSlots } from "~/hooks/useConnectionSlots";
+import type { ConnectionSlot } from "~/hooks/useConnectionSlots";
import { useCurrentOrganization } from "~/hooks/useOrganizations";
import { ApiLogoIcon } from "../code/ApiLogoIcon";
import { List } from "../layout/List";
-import { Body } from "../primitives/text/Body";
import { Header3 } from "../primitives/text/Headers";
import { SubTitle } from "../primitives/text/SubTitle";
import { ConnectionSelector } from "./ConnectionSelector";
-export function WorkflowConnections() {
+export function WorkflowConnections({
+ className,
+ connectionSlots,
+}: {
+ className?: string;
+ connectionSlots: ConnectionSlot[];
+}) {
const organization = useCurrentOrganization();
invariant(organization, "Organization not found");
- const connectionSlots = useConnectionSlots();
- invariant(connectionSlots, "Connection slots not found");
- const allApisCount =
- connectionSlots.services.length + (connectionSlots.source ? 1 : 0);
- const connectedApisCount =
- connectionSlots.services.filter((c) => c.connection).length +
- (connectionSlots.source?.connection ? 1 : 0);
- const unconnectedApisCount = allApisCount - connectedApisCount ? 1 : 0;
- const unconnectedApisCountCopy = `, ${unconnectedApisCount} to connect`;
return (
- <>
-
- <>
- {connectedApisCount} API
- {connectedApisCount > 1
- ? "s"
- : connectedApisCount === 0
- ? "s"
- : ""}{" "}
- connected
- {unconnectedApisCount === 0 ? "" : unconnectedApisCountCopy}
- >
-
+
+
API Connections
- {connectionSlots.source && (
-
-
-
- {connectionSlots.source.integration.name}
-
-
-
- )}
- {connectionSlots.services.map((slot) => (
+ {connectionSlots.map((slot) => (
-
+
{slot.integration?.name}
@@ -80,7 +51,18 @@ export function WorkflowConnections() {
))}
+
+ You will be able to authenticate APIs on demand when your workflow
+ runs. You can also{" "}
+
+ connect them now
+
+ .
+
- >
+
);
}
diff --git a/apps/webapp/app/components/primitives/IconPlug.tsx b/apps/webapp/app/components/primitives/IconPlug.tsx
new file mode 100644
index 00000000000..f07a313ae64
--- /dev/null
+++ b/apps/webapp/app/components/primitives/IconPlug.tsx
@@ -0,0 +1,25 @@
+export function PlugIcon({
+ className,
+ fill,
+}: {
+ className?: string;
+ fill?: string;
+}) {
+ return (
+
+ );
+}
diff --git a/apps/webapp/app/hooks/useConnectionSlots.ts b/apps/webapp/app/hooks/useConnectionSlots.ts
index a4dc0892da2..26b5f46ad8f 100644
--- a/apps/webapp/app/hooks/useConnectionSlots.ts
+++ b/apps/webapp/app/hooks/useConnectionSlots.ts
@@ -1,7 +1,16 @@
+import type { APIConnection } from ".prisma/client";
+import type { ServiceMetadata } from "@trigger.dev/integration-sdk";
import type { UseDataFunctionReturn } from "remix-typedjson/dist/remix";
import type { loader } from "~/routes/__app/orgs/$organizationSlug/workflows/$workflowSlug";
import { hydrateObject, useMatchesData } from "~/utils";
+export type ConnectionSlot = {
+ id: string;
+ connection: APIConnection | null;
+ integration: ServiceMetadata;
+ possibleConnections: APIConnection[];
+};
+
export function useConnectionSlots() {
const routeMatch = useMatchesData(
"routes/__app/orgs/$organizationSlug/workflows/$workflowSlug"
diff --git a/apps/webapp/app/presenters/workflowRunListPresenter.server.ts b/apps/webapp/app/presenters/workflowRunListPresenter.server.ts
index 5a4bd5daa08..1ad00dba175 100644
--- a/apps/webapp/app/presenters/workflowRunListPresenter.server.ts
+++ b/apps/webapp/app/presenters/workflowRunListPresenter.server.ts
@@ -72,6 +72,29 @@ export class WorkflowRunListPresenter {
},
});
+ const totalRealRuns = await this.#prismaClient.workflowRun.count({
+ where: {
+ isTest: false,
+ workflow: {
+ slug: workflowSlug,
+ organization: {
+ slug: organizationSlug,
+ users: {
+ some: {
+ id: userId,
+ },
+ },
+ },
+ },
+ environment: {
+ slug: environmentSlug,
+ },
+ status: {
+ in: statuses,
+ },
+ },
+ });
+
const runs = await this.#prismaClient.workflowRun.findMany({
select: {
id: true,
@@ -111,6 +134,7 @@ export class WorkflowRunListPresenter {
page,
pageCount: Math.ceil(total / pageSize),
total,
+ totalRealRuns,
filters: {
statuses,
},
diff --git a/apps/webapp/app/routes/__app/orgs/$organizationSlug/workflows/$workflowSlug/index.tsx b/apps/webapp/app/routes/__app/orgs/$organizationSlug/workflows/$workflowSlug/index.tsx
index 1f3057eb3e3..4233ce18832 100644
--- a/apps/webapp/app/routes/__app/orgs/$organizationSlug/workflows/$workflowSlug/index.tsx
+++ b/apps/webapp/app/routes/__app/orgs/$organizationSlug/workflows/$workflowSlug/index.tsx
@@ -1,10 +1,25 @@
-import { InformationCircleIcon } from "@heroicons/react/24/outline";
+import { EventRule } from ".prisma/client";
+import { Disclosure } from "@headlessui/react";
+import { BeakerIcon } from "@heroicons/react/20/solid";
+import {
+ ArrowsRightLeftIcon,
+ ChevronDownIcon,
+ Cog6ToothIcon,
+ InformationCircleIcon,
+} from "@heroicons/react/24/outline";
import type { LoaderArgs } from "@remix-run/server-runtime";
-import { typedjson, useTypedLoaderData } from "remix-typedjson";
+import classNames from "classnames";
+import {
+ typedjson,
+ UseDataFunctionReturn,
+ useTypedLoaderData,
+} from "remix-typedjson";
import invariant from "tiny-invariant";
+import { ApiLogoIcon } from "~/components/code/ApiLogoIcon";
import CodeBlock from "~/components/code/CodeBlock";
import { CopyTextButton } from "~/components/CopyTextButton";
import { OctoKitty } from "~/components/GitHubLoginButton";
+import { ConnectionSelector } from "~/components/integrations/ConnectionSelector";
import { WorkflowConnections } from "~/components/integrations/WorkflowConnections";
import { Panel } from "~/components/layout/Panel";
import { PanelHeader } from "~/components/layout/PanelHeader";
@@ -15,6 +30,7 @@ import {
SecondaryLink,
TertiaryLink,
} from "~/components/primitives/Buttons";
+import { PlugIcon } from "~/components/primitives/IconPlug";
import { Input } from "~/components/primitives/Input";
import { Body } from "~/components/primitives/text/Body";
import { Header2 } from "~/components/primitives/text/Headers";
@@ -62,7 +78,8 @@ export const loader = async ({ request, params }: LoaderArgs) => {
};
export default function Page() {
- const { runs, total, hasFilters } = useTypedLoaderData();
+ const { runs, total, totalRealRuns, hasFilters } =
+ useTypedLoaderData();
const organization = useCurrentOrganization();
invariant(organization, "Organization not found");
@@ -106,7 +123,7 @@ export default function Page() {
{workflow.organizationTemplate && (
@@ -125,79 +142,7 @@ export default function Page() {