Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions packages/ui/src/features/folder-picker/GitHubRepoPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function GitHubRepoPicker({
repositories,
isLoading,
placeholder = "Select repository...",
size = "1",
disabled = false,
anchor,
showSearchInput = true,
Expand All @@ -55,6 +56,8 @@ export function GitHubRepoPicker({
hasMore: controlledHasMore,
onLoadMore,
}: GitHubRepoPickerProps) {
const buttonSize = size === "2" ? "lg" : "sm";
const buttonTextClass = size === "2" ? "text-[13px]" : "";
const triggerRef = useRef<HTMLButtonElement>(null);
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
const [uncontrolledSearchQuery, setUncontrolledSearchQuery] = useState("");
Expand Down Expand Up @@ -91,7 +94,12 @@ export function GitHubRepoPicker({

if (isLoading && !showInlineLoadingState) {
return (
<Button variant="outline" disabled size="sm">
<Button
variant="outline"
disabled
size={buttonSize}
className={buttonTextClass}
>
<GithubLogo size={16} weight="regular" className="shrink-0" />
Loading repos...
</Button>
Expand All @@ -107,7 +115,12 @@ export function GitHubRepoPicker({
!hasActiveRemoteSearch
) {
return (
<Button variant="outline" disabled size="sm">
<Button
variant="outline"
disabled
size={buttonSize}
className={buttonTextClass}
>
<GithubLogo size={16} weight="regular" className="shrink-0" />
No GitHub repos
</Button>
Expand All @@ -121,10 +134,10 @@ export function GitHubRepoPicker({
<Button
type="button"
variant="outline"
size="sm"
size={buttonSize}
disabled
aria-label="Repository"
className="pointer-events-none min-w-0 max-w-full cursor-default justify-start disabled:opacity-100"
className={`pointer-events-none min-w-0 max-w-full cursor-default justify-start disabled:opacity-100 ${buttonTextClass}`}
>
<GithubLogo size={14} weight="regular" className="shrink-0" />
<span className="min-w-0 truncate">{onlyRepo}</span>
Expand Down Expand Up @@ -165,9 +178,10 @@ export function GitHubRepoPicker({
<Button
ref={triggerRef}
variant="outline"
size="sm"
size={buttonSize}
disabled={disabled}
aria-label="Repository"
className={buttonTextClass}
>
<GithubLogo size={14} weight="regular" className="shrink-0" />
<span className="min-w-0 truncate">{value ?? placeholder}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function LoopBehaviorFields({
<Flex
direction="column"
gap="2"
className="rounded-(--radius-2) border border-border bg-(--color-panel-solid) p-3"
className="rounded-(--radius-2) border border-border bg-(--gray-1) p-3"
>
<Flex align="center" justify="between" gap="2">
<Flex direction="column" gap="0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function LoopContextFields({
};

const contextOptions = [
{ value: NOT_ATTACHED_VALUE, label: "Not attached" },
{ value: NOT_ATTACHED_VALUE, label: "Not attached to a channel" },
...channels.map((channel) => ({
value: channel.id,
label: `#${channel.name}`,
Expand All @@ -63,15 +63,16 @@ export function LoopContextFields({
value={value?.folderId ?? NOT_ATTACHED_VALUE}
options={contextOptions}
disabled={disabled}
ariaLabel="Context"
size="lg"
ariaLabel="Context channel"
onValueChange={selectContext}
/>

{value ? (
<Flex
direction="column"
gap="3"
className="rounded-(--radius-2) border border-border bg-(--color-panel-solid) p-3"
className="rounded-(--radius-2) border border-border bg-(--gray-1) p-3"
>
<ToggleRow
title="Show runs in the feed"
Expand Down Expand Up @@ -110,6 +111,7 @@ export function LoopContextFields({
label: dashboard.name,
}))}
disabled={disabled}
size="lg"
ariaLabel="Canvas"
onValueChange={(canvasId) =>
patchOutputs({ canvas_id: canvasId })
Expand Down
91 changes: 59 additions & 32 deletions packages/ui/src/features/loops/components/LoopForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import {
Check,
} from "@phosphor-icons/react";
import { type LoopSchemas, LoopsApiError } from "@posthog/api-client/loops";
import { PROJECT_BLUEBIRD_FLAG } from "@posthog/shared";
import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFlag";
import { SettingsOptionSelect } from "@posthog/ui/features/settings/SettingsOptionSelect";
import { useSidebarStore } from "@posthog/ui/features/sidebar/sidebarStore";
import { useSetHeaderContent } from "@posthog/ui/hooks/useSetHeaderContent";
import { Button } from "@posthog/ui/primitives/Button";
import { toast } from "@posthog/ui/primitives/toast";
Expand Down Expand Up @@ -83,6 +86,17 @@ export function LoopForm({ loop }: LoopFormProps) {
if (!loop) useLoopDraftStore.getState().setPrefill(null);
}, [loop]);

// Contexts are a channels surface; hide the attachment UI when channels are
// off, unless this loop is already attached so the link stays visible and
// detachable.
const bluebirdEnabled = useFeatureFlag(
PROJECT_BLUEBIRD_FLAG,
import.meta.env.DEV,
);
const channelsEnabled =
useSidebarStore((s) => s.channelsEnabled) && bluebirdEnabled;
const showContextField = channelsEnabled || !!values.contextTarget;

const createLoop = useCreateLoop();
const updateLoop = useUpdateLoop(loop?.id ?? "");
const isSubmitting = isEdit ? updateLoop.isPending : createLoop.isPending;
Expand Down Expand Up @@ -157,13 +171,13 @@ export function LoopForm({ loop }: LoopFormProps) {
<Box className="flex h-full items-center justify-center p-6">
<Flex
direction="column"
className="max-h-full w-full max-w-[600px] overflow-hidden rounded-(--radius-3) border border-border bg-(--color-panel-solid) shadow-xl"
className="max-h-full w-full max-w-[640px] overflow-hidden rounded-(--radius-3) border border-border bg-(--color-panel-solid) shadow-xl"
>
<Box className="border-border border-b px-5 pt-5 pb-4">
<Box className="border-border border-b px-6 pt-5 pb-4">
<Stepper current={step} complete={stepComplete} onSelect={setStep} />
</Box>

<Box className="min-h-0 flex-1 overflow-y-auto px-5 py-5">
<Box className="min-h-0 flex-1 overflow-y-auto px-6 py-6">
{step === 0 ? (
<Step
title="What should this loop do?"
Expand Down Expand Up @@ -203,7 +217,7 @@ export function LoopForm({ loop }: LoopFormProps) {
{step === 1 ? (
<Step
title="When should it run?"
description="Add one or more triggers. Leave empty to run it only on demand."
description="A loop can have several triggers, and any one of them starts a run. With no triggers, you run it yourself from the loop's page."
>
<LoopTriggerEditor
triggers={values.triggers}
Expand All @@ -224,14 +238,15 @@ export function LoopForm({ loop }: LoopFormProps) {
className="max-w-[340px]"
hint={
values.contextTarget
? "Loops attached to a context post runs to its shared feed, so they're visible to everyone on the project."
? "Loops attached to a channel post runs to its shared feed, so they're visible to everyone on the project."
: undefined
}
>
<SettingsOptionSelect
value={values.visibility}
options={VISIBILITY_OPTIONS}
disabled={isSubmitting || !!values.contextTarget}
size="lg"
ariaLabel="Visibility"
onValueChange={(value) =>
patch({
Expand All @@ -243,35 +258,39 @@ export function LoopForm({ loop }: LoopFormProps) {

<Divider />

<Field
label="Context"
hint="Attach this loop to a context to file its runs in the feed and keep its context.md or a canvas up to date."
>
<LoopContextFields
value={values.contextTarget}
disabled={isSubmitting}
onChange={(contextTarget) =>
patch(
contextTarget
? { contextTarget, visibility: "team" }
: { contextTarget },
)
}
/>
</Field>
{showContextField ? (
<>
<Field
label="Context"
hint="A context is one of the channels in your sidebar. Attach this loop to a channel and its runs show up in that channel's feed; it can also keep the channel's context.md or a canvas up to date."
>
<LoopContextFields
value={values.contextTarget}
disabled={isSubmitting}
onChange={(contextTarget) =>
patch(
contextTarget
? { contextTarget, visibility: "team" }
: { contextTarget },
)
}
/>
</Field>

<Divider />
<Divider />
</>
) : null}

<Field
label="Repository"
label="Base repository"
hint={
values.repositories.length > 1
? `${values.repositories.length - 1} more ${
values.repositories.length === 2
? "repository stays"
: "repositories stay"
} attached to this loop.`
: "Optional. Leave empty for a report-only loop that works purely through connectors."
: "The repository runs check out and work in. Optional. Leave empty for a report-only loop that works purely through connectors."
}
>
<LoopRepositoryPicker
Expand Down Expand Up @@ -352,7 +371,7 @@ export function LoopForm({ loop }: LoopFormProps) {
title="Review"
description="Check everything before you create the loop."
>
<ReviewList values={values} />
<ReviewList values={values} showContext={showContextField} />
</Step>
) : null}
</Box>
Expand Down Expand Up @@ -497,7 +516,13 @@ function Divider() {
return <Box className="h-px bg-(--gray-4)" />;
}

function ReviewList({ values }: { values: LoopFormValues }) {
function ReviewList({
values,
showContext,
}: {
values: LoopFormValues;
showContext: boolean;
}) {
const reasoning = values.reasoningEffort ?? "auto";
const channels = (["push", "email", "slack"] as const).filter(
(channel) => values.notifications[channel]?.enabled,
Expand All @@ -524,12 +549,14 @@ function ReviewList({ values }: { values: LoopFormValues }) {
values.model || "Default model"
} · ${reasoning} reasoning`}
/>
{showContext ? (
<ReviewRow
label="Context"
value={describeContext(values.contextTarget)}
/>
) : null}
<ReviewRow
label="Context"
value={describeContext(values.contextTarget)}
/>
<ReviewRow
label="Repository"
label="Base repository"
value={
values.repositories.length > 0
? values.repositories.map((repo) => repo.full_name).join(", ")
Expand Down Expand Up @@ -580,7 +607,7 @@ function ReviewRow({
}

function describeContext(target: LoopContextTargetDraft | null): string {
if (!target) return "Not attached";
if (!target) return "Not attached to a channel";
const outputs: string[] = [];
if (target.outputs.post_to_feed) outputs.push("feed");
if (target.outputs.update_context) outputs.push("context.md");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ export function Field({
children: ReactNode;
}) {
return (
<Flex direction="column" gap="1" className={className}>
<Text as="label" className="font-medium text-[12px] text-gray-11">
<Flex direction="column" gap="2" className={className}>
<Text as="label" className="font-medium text-[13px] text-gray-12">
{label}
{required ? <span className="ml-0.5 text-(--accent-9)">*</span> : null}
</Text>
{children}
{hint ? (
<Text className="text-[11px] text-gray-10 leading-snug">{hint}</Text>
<Text className="text-[12px] text-gray-10 leading-snug">{hint}</Text>
) : null}
</Flex>
);
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/src/features/loops/components/LoopModelFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export function LoopModelFields({
onModelChange(value === DEFAULT_MODEL_VALUE ? "" : value)
}
disabled={disabled}
size="lg"
ariaLabel="Model"
/>
</Field>
Expand All @@ -113,6 +114,7 @@ export function LoopModelFields({
onAdapterChange(value as LoopSchemas.LoopRuntimeAdapterEnum)
}
disabled={disabled}
size="lg"
ariaLabel="Adapter"
/>
</Field>
Expand All @@ -129,6 +131,7 @@ export function LoopModelFields({
)
}
disabled={disabled}
size="lg"
ariaLabel="Reasoning effort"
/>
</Field>
Expand Down
Loading
Loading