Skip to content
Open
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
41 changes: 8 additions & 33 deletions src/components/Learn/FeaturedTours.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useQueryClient } from "@tanstack/react-query";
import { Link, useNavigate } from "@tanstack/react-router";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
import { BlockStack, InlineStack } from "@/components/ui/layout";
Expand All @@ -11,35 +10,31 @@ import { resetAllTourPipelineState } from "@/providers/TourProvider/tourPipeline
import { APP_ROUTES } from "@/routes/router";
import { tracking } from "@/utils/tracking";

import { TourCompletedBadge } from "./TourCompletedBadge";
import { tours as tourCards } from "./tours";
import { getTour } from "./tours/registry";

interface FeaturedTour {
id: string;
title: string;
duration: string;
tag?: "new" | "popular";
available: boolean;
}

const FEATURED_TOUR_IDS: Array<Pick<FeaturedTour, "id" | "tag">> = [
{ id: "navigating-the-editor", tag: "new" },
{ id: "first-pipeline", tag: "popular" },
{ id: "using-secrets" },
{ id: "multinode-tasks" },
const FEATURED_TOUR_IDS: string[] = [
"navigating-the-editor",
"first-pipeline",
"using-secrets",
"subgraphs",
];

function buildFeaturedTours(): FeaturedTour[] {
return FEATURED_TOUR_IDS.flatMap(({ id, tag }) => {
return FEATURED_TOUR_IDS.flatMap((id) => {
const card = tourCards.find((c) => c.id === id);
if (!card) return [];
return [
{
id,
title: card.title,
duration: card.duration,
tag,
available: getTour(id) !== undefined,
},
];
});
Expand Down Expand Up @@ -106,7 +101,6 @@ function FeaturedTourButton({
<Button
variant="ghost"
size="lg"
disabled={!tour.available}
onClick={onStart}
className="h-auto min-h-10 w-full justify-start whitespace-normal py-2 text-left"
{...tracking("learning_hub.tours.start", {
Expand Down Expand Up @@ -146,26 +140,7 @@ function FeaturedTourLabel({
<Paragraph size="sm" weight="semibold" className="truncate">
{tour.title}
</Paragraph>
{tour.tag && (
<Badge
size="sm"
variant={tour.tag === "new" ? "default" : "secondary"}
className="capitalize"
>
{tour.tag}
</Badge>
)}
{completed && (
<Badge size="sm" variant="outline" className="text-green-600">
<Icon name="Check" size="xs" aria-hidden="true" />
Completed
</Badge>
)}
{!tour.available && (
<Badge size="sm" variant="outline">
Coming soon
</Badge>
)}
{completed && <TourCompletedBadge />}
</InlineStack>
<Text size="xs" tone="subdued">
{tour.duration}
Expand Down
11 changes: 11 additions & 0 deletions src/components/Learn/TourCompletedBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Badge } from "@/components/ui/badge";
import { Icon } from "@/components/ui/icon";

export function TourCompletedBadge() {
return (
<Badge size="sm" variant="outline" className="text-green-600">
<Icon name="Check" size="xs" aria-hidden="true" />
Completed
</Badge>
);
}
61 changes: 21 additions & 40 deletions src/components/Learn/ToursLibrary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { resetAllTourPipelineState } from "@/providers/TourProvider/tourPipeline
import { APP_ROUTES } from "@/routes/router";
import { tracking } from "@/utils/tracking";

import { TourCompletedBadge } from "./TourCompletedBadge";
import {
type Tour,
TOUR_DIFFICULTY_BLURBS,
Expand All @@ -27,10 +28,8 @@ import {
type TourDifficulty,
tours,
} from "./tours";
import { getTour } from "./tours/registry";

function TourCard({ tour }: { tour: Tour }) {
const isAvailable = getTour(tour.id) !== undefined;
const completed = useTourCompletion(tour.id);
const navigate = useNavigate();
const queryClient = useQueryClient();
Expand All @@ -44,7 +43,7 @@ function TourCard({ tour }: { tour: Tour }) {
};

return (
<Card className="h-full py-4 gap-2 hover:border-primary/40 hover:shadow-md transition-all duration-200">
<Card className="h-full py-4 gap-2">
<CardHeader className="px-4 gap-2">
<CardTitle className="text-sm leading-snug">{tour.title}</CardTitle>
<CardDescription className="text-sm">
Expand All @@ -57,43 +56,27 @@ function TourCard({ tour }: { tour: Tour }) {
<Badge size="sm" variant="secondary">
{tour.area}
</Badge>
{completed && (
<Badge size="sm" variant="outline" className="text-green-600">
<Icon name="Check" size="xs" aria-hidden="true" />
Completed
</Badge>
)}
{completed && <TourCompletedBadge />}
<Text size="xs" tone="subdued">
{tour.duration}
</Text>
</InlineStack>
{isAvailable ? (
<Button
<Button
size="sm"
variant="ghost"
onClick={startTour}
{...tracking("learning_hub.tours.start", {
tour_id: tour.id,
is_restart: completed,
})}
>
{completed ? "Restart" : "Start tour"}
<Icon
name={completed ? "RotateCcw" : "Play"}
size="sm"
variant="ghost"
onClick={startTour}
{...tracking("learning_hub.tours.start", {
tour_id: tour.id,
is_restart: completed,
})}
>
{completed ? "Restart" : "Start tour"}
<Icon
name={completed ? "RotateCcw" : "Play"}
size="sm"
aria-hidden="true"
/>
</Button>
) : (
<Button
size="sm"
variant="ghost"
disabled
{...tracking("learning_hub.tours.start", { tour_id: tour.id })}
>
Coming soon
</Button>
)}
aria-hidden="true"
/>
</Button>
</InlineStack>
</CardContent>
</Card>
Expand All @@ -107,10 +90,6 @@ function DifficultySection({
difficulty: TourDifficulty;
tours: Tour[];
}) {
if (difficultyTours.length === 0) {
return null;
}

return (
<BlockStack gap="3">
<BlockStack gap="1">
Expand Down Expand Up @@ -150,7 +129,9 @@ export function ToursLibrary() {

return (
<BlockStack gap="8">
{TOUR_DIFFICULTY_ORDER.map((difficulty) => (
{TOUR_DIFFICULTY_ORDER.filter(
(difficulty) => (buckets.get(difficulty)?.length ?? 0) > 0,
).map((difficulty) => (
<DifficultySection
key={difficulty}
difficulty={difficulty}
Expand Down
60 changes: 2 additions & 58 deletions src/components/Learn/tours.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,16 @@
"title": "Find your way around the editor",
"description": "Get oriented with the canvas, dockable panels, properties view, canvas tools, and the menu bar so you know where every feature lives.",
"difficulty": "Beginner",
"duration": "5 min",
"area": "Editor"
},
{
"id": "component-library-basics",
"title": "Browse the component library",
"description": "Learn how to search, filter, favorite, and inspect components before dropping them on the canvas.",
"difficulty": "Beginner",
"duration": "3 min",
"area": "Library"
},
{
"id": "submitting-and-monitoring",
"title": "Submit a run and watch it execute",
"description": "Submit a pipeline, locate it in All Runs, and step through task logs as the run progresses.",
"difficulty": "Beginner",
"duration": "4 min",
"area": "Runs"
"area": "Editor"
},
{
"id": "using-secrets",
"title": "Use secrets without leaking credentials",
"description": "Create a secret, reference it from a task via the lightning-bolt menu, and confirm it never lands in the pipeline YAML.",
"difficulty": "Intermediate",
"duration": "3 min",
"area": "Security"
},
{
"id": "create-component",
"title": "Create a custom component in the browser",
"description": "Author a Python or YAML component using the in-app editor, no local development environment required.",
"difficulty": "Intermediate",
"duration": "5 min",
"area": "Library"
"area": "Security"
},
{
"id": "subgraphs",
Expand All @@ -54,37 +30,5 @@
"difficulty": "Intermediate",
"duration": "5 min",
"area": "Editor"
},
{
"id": "templatized-run-names",
"title": "Templatize run names for experiment tracking",
"description": "Enable the beta feature and craft a name template so every submitted run gets a unique, scannable label.",
"difficulty": "Intermediate",
"duration": "3 min",
"area": "Runs"
},
{
"id": "caching",
"title": "Save hours with task-level caching",
"description": "Understand how cache keys are derived, when caches hit, and how to disable caching on a single task when you must.",
"difficulty": "Advanced",
"duration": "5 min",
"area": "Performance"
},
{
"id": "github-libraries",
"title": "Register a GitHub repo as a component source",
"description": "Point Tangle at a shared GitHub repository to pull in an entire library of team components at once.",
"difficulty": "Advanced",
"duration": "4 min",
"area": "Library"
},
{
"id": "multinode-tasks",
"title": "Configure a multi-node task",
"description": "Set the multi-node annotation, reference per-node system data, and submit a distributed run across multiple workers.",
"difficulty": "Advanced",
"duration": "6 min",
"area": "Compute"
}
]
Loading