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
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
"skillsInstallation": false,
"workspaceScheduling": false,
"remoteSsh": false,
"linearIntegration": false
"linearIntegration": false,
"logs": false,
"checks": false,
"browser": false
},
"env": {
"dev": {
Expand Down
12 changes: 12 additions & 0 deletions src-tauri/src/commands/checks.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::core::checks_logs::{LogBucket, LogQuery, LogRecordView, SqlResult};
use crate::core::{JobResult, RunSummary, SetupScriptStatus, WorkflowInfo};
use crate::AppState;
use tauri::State;

#[tauri::command]
pub async fn list_workflows(repo_path: String) -> Result<Vec<WorkflowInfo>, String> {
Expand All @@ -10,12 +12,17 @@ pub async fn list_workflows(repo_path: String) -> Result<Vec<WorkflowInfo>, Stri

#[tauri::command]
pub async fn run_workflow_job(
state: State<'_, AppState>,
repo_path: String,
filename: String,
job_id: String,
workspace_id: i64,
workspace_path: String,
) -> Result<JobResult, String> {
crate::commands::feature_preview::require(
&state,
crate::core::feature_preview::PreviewFeature::Checks,
)?;
tauri::async_runtime::spawn_blocking(move || {
crate::core::run_workflow_job_sync(
&repo_path,
Expand All @@ -31,11 +38,16 @@ pub async fn run_workflow_job(

#[tauri::command]
pub async fn run_workflow(
state: State<'_, AppState>,
repo_path: String,
filename: String,
workspace_id: i64,
workspace_path: String,
) -> Result<Vec<JobResult>, String> {
crate::commands::feature_preview::require(
&state,
crate::core::feature_preview::PreviewFeature::Checks,
)?;
tauri::async_runtime::spawn_blocking(move || {
crate::core::run_workflow_sync(&repo_path, &filename, workspace_id, &workspace_path)
})
Expand Down
9 changes: 9 additions & 0 deletions src-tauri/src/core/feature_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub enum PreviewFeature {
WorkspaceScheduling,
RemoteSsh,
LinearIntegration,
Logs,
Checks,
Browser,
}

impl PreviewFeature {
Expand All @@ -20,6 +23,9 @@ impl PreviewFeature {
Self::WorkspaceScheduling => "workspaceScheduling",
Self::RemoteSsh => "remoteSsh",
Self::LinearIntegration => "linearIntegration",
Self::Logs => "logs",
Self::Checks => "checks",
Self::Browser => "browser",
}
}

Expand Down Expand Up @@ -98,6 +104,9 @@ mod tests {
assert!(!package_json_default(PreviewFeature::WorkspaceScheduling));
assert!(!package_json_default(PreviewFeature::RemoteSsh));
assert!(!package_json_default(PreviewFeature::LinearIntegration));
assert!(!package_json_default(PreviewFeature::Logs));
assert!(!package_json_default(PreviewFeature::Checks));
assert!(!package_json_default(PreviewFeature::Browser));
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src/components/LogsTimeseriesChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function LogsTimeseriesChart({
const index = new Map(timeline.map((t, i) => [t, i]));

const series = SERIES.map(({ severity, label, color }) => {
const data = new Array<number>(timeline.length).fill(0);
const data = Array.from<number>({ length: timeline.length }).fill(0);
for (const bucket of buckets) {
if (bucket.severity_text !== severity) continue;
const at = index.get(Date.parse(bucket.bucket));
Expand Down
38 changes: 29 additions & 9 deletions src/components/ShowWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ export const ShowWorkspace = ({
const { addToast } = useToast();
const workspaceScheduling = usePreviewFeature("workspaceScheduling");
const linearIntegration = usePreviewFeature("linearIntegration");
const logsEnabled = usePreviewFeature("logs");
const checksEnabled = usePreviewFeature("checks");
const browserEnabled = usePreviewFeature("browser");
const { fontSize } = useTerminalSettingsStore();

const { data: remoteInfo } = useGitRemoteInfo(effectiveRepoPath || undefined);
Expand Down Expand Up @@ -326,6 +329,21 @@ export const ShowWorkspace = ({
const [scrollToCommitId, setScrollToCommitId] = useState<string | null>(null);
const [showFileBrowserInCode, setShowFileBrowserInCode] = useState(false);

useEffect(() => {
if (
(!logsEnabled && activeTab === "logs") ||
(!checksEnabled && activeTab === "checks")
) {
setActiveTab("overview");
}
}, [activeTab, checksEnabled, logsEnabled]);

useEffect(() => {
if (!browserEnabled && reviewSubView === "browser") {
setReviewSubView("diff");
}
}, [browserEnabled, reviewSubView]);

// `treq send --browser <url-or-file>` opens the Browser view directly,
// instead of showing an attachment preview like image/text sends do.
const treqSendAssets = useTreqSendStore((s) => s.assets);
Expand Down Expand Up @@ -1294,14 +1312,16 @@ export const ShowWorkspace = ({
</span>
)}
</TabsTrigger>
<TabsTrigger
value="checks"
className="inline-flex items-center gap-1.5"
>
<Workflow className="w-4 h-4" />
<span>Checks</span>
</TabsTrigger>
{!workspace && (
{checksEnabled && (
<TabsTrigger
value="checks"
className="inline-flex items-center gap-1.5"
>
<Workflow className="w-4 h-4" />
<span>Checks</span>
</TabsTrigger>
)}
{!workspace && logsEnabled && (
<TabsTrigger
value="logs"
className="inline-flex items-center gap-1.5"
Expand All @@ -1312,7 +1332,7 @@ export const ShowWorkspace = ({
)}
</TabsList>
</Tabs>
{activeTab === "changes" && (
{activeTab === "changes" && browserEnabled && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
Expand Down
11 changes: 10 additions & 1 deletion src/lib/features.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import {
} from "./features";

describe("preview features", () => {
it("lists the four preview features with docs paths", () => {
it("lists all preview features with docs paths", () => {
expect(PREVIEW_FEATURES.map((feature) => feature.id)).toEqual([
"skillsInstallation",
"workspaceScheduling",
"remoteSsh",
"linearIntegration",
"logs",
"checks",
"browser",
]);
for (const feature of PREVIEW_FEATURES) {
expect(feature.docsPath.startsWith("/docs/")).toBe(true);
Expand All @@ -26,13 +29,19 @@ describe("preview features", () => {
expect(FEATURES.workspaceScheduling).toBe(false);
expect(FEATURES.remoteSsh).toBe(false);
expect(FEATURES.linearIntegration).toBe(false);
expect(FEATURES.logs).toBe(false);
expect(FEATURES.checks).toBe(false);
expect(FEATURES.browser).toBe(false);
});

it("defaults every preview flag on in test and dev", () => {
expect(previewFeatureDefault("skillsInstallation")).toBe(true);
expect(previewFeatureDefault("workspaceScheduling")).toBe(true);
expect(previewFeatureDefault("remoteSsh")).toBe(true);
expect(previewFeatureDefault("linearIntegration")).toBe(true);
expect(previewFeatureDefault("logs")).toBe(true);
expect(previewFeatureDefault("checks")).toBe(true);
expect(previewFeatureDefault("browser")).toBe(true);
});

it("honors stored true/false over the startup default", () => {
Expand Down
18 changes: 18 additions & 0 deletions src/lib/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export const PREVIEW_FEATURE_IDS = [
"workspaceScheduling",
"remoteSsh",
"linearIntegration",
"logs",
"checks",
"browser",
] as const;

export type PreviewFeatureId = (typeof PREVIEW_FEATURE_IDS)[number];
Expand Down Expand Up @@ -38,6 +41,21 @@ export const PREVIEW_FEATURES: readonly PreviewFeature[] = [
title: "Linear integration",
docsPath: "/docs/concepts/linear-integration",
},
{
id: "logs",
title: "Logs",
docsPath: "/docs/concepts/logs",
},
{
id: "checks",
title: "Checks",
docsPath: "/docs/concepts/checks",
},
{
id: "browser",
title: "Browser",
docsPath: "/docs/concepts/browser",
},
];

export function previewSettingKey(id: PreviewFeatureId): string {
Expand Down
3 changes: 3 additions & 0 deletions src/stores/featurePreviewStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ function defaultFlags(): PreviewFlags {
workspaceScheduling: previewFeatureDefault("workspaceScheduling"),
remoteSsh: previewFeatureDefault("remoteSsh"),
linearIntegration: previewFeatureDefault("linearIntegration"),
logs: previewFeatureDefault("logs"),
checks: previewFeatureDefault("checks"),
browser: previewFeatureDefault("browser"),
};
}

Expand Down
52 changes: 50 additions & 2 deletions test/integration/feature-preview.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import * as React from "react";
import { beforeEach, describe, expect, it } from "vitest";
import { createTestRepo, openRepo } from "../utils";
import {
createTestRepo,
findSidebarBranchElement,
openRepo,
writeRepoFile,
} from "../utils";
import { render, screen } from "../test-utils";
import { Dashboard } from "../../src/components/Dashboard";
import userEvent from "@testing-library/user-event";
import { createWorkspace, scheduleWorkspaces } from "../../src/lib/api";
import {
createWorkspace,
runWorkflow,
scheduleWorkspaces,
setSetting,
trustRepo,
} from "../../src/lib/api";
import { previewSettingKey } from "../../src/lib/features";

describe("feature preview settings", () => {
Expand Down Expand Up @@ -35,6 +46,27 @@ describe("feature preview settings", () => {
screen.getByRole("button", { name: "Workspace scheduling" }),
).toBeTruthy();
expect(screen.getByRole("button", { name: "Remote SSH" })).toBeTruthy();
expect(screen.getByRole("button", { name: "Logs" })).toBeTruthy();
expect(screen.getByRole("button", { name: "Checks" })).toBeTruthy();
expect(screen.getByRole("button", { name: "Browser" })).toBeTruthy();
});

it("hides Logs, Checks, and the Diff/Browser switcher when disabled", async () => {
await createWorkspace(repoPath, "feat/hidden-previews");
await openFeaturePreview();
await user.click(screen.getByLabelText("Logs"));
await user.click(screen.getByLabelText("Checks"));
await user.click(screen.getByLabelText("Browser"));
await user.click(screen.getByRole("button", { name: "Close" }));

expect(screen.queryByRole("tab", { name: /^Logs/ })).toBeNull();
expect(screen.queryByRole("tab", { name: /^Checks/ })).toBeNull();

await user.click(await findSidebarBranchElement("feat/hidden-previews"));
await user.click(await screen.findByRole("tab", { name: "Changes" }));
expect(
screen.queryByRole("button", { name: "Switch review view" }),
).toBeNull();
});

it("hides the Skills tab when skills installation is off", async () => {
Expand Down Expand Up @@ -89,4 +121,20 @@ describe("feature preview rust gates", () => {
scheduleWorkspaces(repoPath, [workspaceId], "2099-01-01T00:00:00Z"),
).rejects.toThrow(/workspaceScheduling/);
});

it("rejects workflow checks when the preview flag is off", async () => {
const { repoPath } = createTestRepo(false);
const workspaceId = await createWorkspace(repoPath, "feat/checks-gated");
await writeRepoFile(
repoPath,
".treq/workflows/ci.yaml",
"name: CI\non: workflow_dispatch\njobs:\n test:\n steps:\n - run: echo test\n",
);
await trustRepo(repoPath);
await setSetting(previewSettingKey("checks"), "false");

await expect(
runWorkflow(repoPath, "ci.yaml", workspaceId, repoPath),
).rejects.toThrow(/checks/);
});
});
Loading