diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml new file mode 100644 index 000000000..58acf9278 --- /dev/null +++ b/.github/workflows/check-links.yml @@ -0,0 +1,33 @@ +name: Check Links + +on: + workflow_dispatch: + push: + branches: + - main + - future + pull_request: + types: [opened, synchronize, ready_for_review] + branches: + - main + - future +concurrency: + # New commit on branch cancels running workflows of the same branch + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Check Links + run: bash scripts/check-links.sh diff --git a/.gitignore b/.gitignore index cfcdc9031..385c8d6e1 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,6 @@ dist-ssr /playwright/.cache/ .env* -!.env.example \ No newline at end of file +!.env.example + +urls.txt diff --git a/legacy/src/constants/constantCommands.ts b/legacy/src/constants/constantCommands.ts index 3ced92e49..57783e85c 100644 --- a/legacy/src/constants/constantCommands.ts +++ b/legacy/src/constants/constantCommands.ts @@ -74,7 +74,7 @@ export const constantCommandsToCreatePipeline = { export const constantCommandsToCreateRuns = { title: 'Runs Cheatsheet', documentation: - 'https://docs.zenml.io/user-guide/starter-guide/fetch-runs-after-execution', + 'https://docs.zenml.io/how-to/build-pipelines/fetching-pipelines', body: [ { text: 'Create a runs', diff --git a/scripts/check-links.js b/scripts/check-links.js new file mode 100644 index 000000000..1a2f8941e --- /dev/null +++ b/scripts/check-links.js @@ -0,0 +1,50 @@ +import fs from "fs"; +import { exit } from "process"; + +const allowedStatuses = [401, 403, 405]; + +const ignoreList = []; + +const headers = new Headers({ + "User-Agent": "ZenMLURLBot/1.0 (+http://zenml.io/bot)" +}); + +const noIndexRegex = /content="noindex"/i; +const docsRegex = /docs.zenml.io/i; + +async function checkLink() { + let hasFailed = false; + + const links = fs.readFileSync("urls.txt", "utf-8").split("\n").filter(Boolean); + for (const link of links) { + if (ignoreList.includes(link)) { + console.log("\x1b[33m", `Ignoring ${link}`); + continue; + } + try { + const response = await fetch(link, { method: "GET", headers }); + const payload = await response.text(); + const hasNoIndex = noIndexRegex.test(payload); + + if (hasNoIndex && docsRegex.test(link)) { + console.log("\x1b[31m", `[${response.status}] ${link}`); + hasFailed = true; + continue; + } + + if (response.ok || allowedStatuses.includes(response.status)) { + console.log("\x1b[32m", `[${response.status}] ${link}`); + } else { + console.log("\x1b[31m", `[${response.status}] ${link}`); + hasFailed = true; + } + } catch (error) { + console.error("\x1b[31m", `Error fetching ${link}: ${error}`); + hasFailed = true; + } + } + + exit(hasFailed ? 1 : 0); +} + +checkLink(); diff --git a/scripts/check-links.sh b/scripts/check-links.sh new file mode 100644 index 000000000..6b67617c0 --- /dev/null +++ b/scripts/check-links.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Define the file patterns to search for URLs +file_patterns=("*.json" "*.tsx" "*.ts") + +# Define the output file for the URLs +output_file="urls.txt" + +# Find unique URLs matching the specified pattern in the specified file types +find_unique_urls() { + include_patterns="" + for pattern in "${file_patterns[@]}"; do + include_patterns+="--include=${pattern} " + done + grep -E -o 'https?:\/\/([a-zA-Z0-9.-]*\.)?zenml\.io[^"'\''[:space:]]*' -r --no-filename $include_patterns "$@" | sort -u +} + +# Find unique URLs in the specified file patterns within the "src" directory +find_unique_urls src legacy | sort -u > "$output_file" + +# Run the link checker script +node scripts/check-links.js diff --git a/src/app/pipelines/[namespace]/columns.tsx b/src/app/pipelines/[namespace]/columns.tsx index 407cc18cd..dd6056f77 100644 --- a/src/app/pipelines/[namespace]/columns.tsx +++ b/src/app/pipelines/[namespace]/columns.tsx @@ -32,7 +32,7 @@ export function getPipelineDetailColumns(): ColumnDef[] { return (
- +

{name}

diff --git a/src/app/survey/SlackStep.tsx b/src/app/survey/SlackStep.tsx new file mode 100644 index 000000000..5581ab839 --- /dev/null +++ b/src/app/survey/SlackStep.tsx @@ -0,0 +1,85 @@ +import Adam from "@/assets/images/portraits/adam.webp"; +import Alex from "@/assets/images/portraits/alex.webp"; +import Baris from "@/assets/images/portraits/baris.webp"; +import Hamza from "@/assets/images/portraits/hamza.webp"; +import Stefan from "@/assets/images/portraits/stefan.webp"; +import { useSurveyContext } from "@/components/survey/SurveyContext"; +import { Avatar, AvatarFallback, AvatarImage, Button } from "@zenml-io/react-component-library"; + +export function SlackStep() { + const { setSurveyStep } = useSurveyContext(); + + function joinAndContinue() { + window.open("https://zenml.io/slack", "_blank"); + setSurveyStep((prev) => prev + 1); + } + + return ( +
+
+

Join The ZenML Slack Community

+

+ Connect to our growing community and meet fellow ZenML enthusiasts, get support, and share + your insights. Let's grow together! +

+
+ + + +
+ ); +} + +const avatarList = [ + { + name: "Adam", + image: Adam + }, + { + name: "Hamza", + image: Hamza + }, + { + name: "Alex", + image: Alex + }, + { + name: "Stefan", + image: Stefan + }, + { name: "Baris", image: Baris } +]; + +function AvatarStack() { + return ( +
+
+ {avatarList.map((avatar) => ( + + + {avatar.name[0]} + + ))} +
+

+ Adam Probst, Hamza Tahir, and +1,800 others have already joined +

+
+ ); +} diff --git a/src/app/survey/Wizard.tsx b/src/app/survey/Wizard.tsx index c85eb798a..4b1e96ce8 100644 --- a/src/app/survey/Wizard.tsx +++ b/src/app/survey/Wizard.tsx @@ -1,12 +1,13 @@ -import { AccountDetailsStep } from "./AccountDetailsStep"; -import { PrimaryUseStep } from "./PrimaryUseStep"; -import { AwarenessStep } from "./AwarenessStep"; import StepDisplay from "@/components/survey/StepDisplay"; import { useSurveyContext } from "@/components/survey/SurveyContext"; import { useCurrentUser } from "@/data/users/current-user-query"; import { Skeleton } from "@zenml-io/react-component-library"; -import { SurveyUserProvider } from "./SurveyUserContext"; import { SuccessStep } from "../../components/survey/SuccessStep"; +import { AccountDetailsStep } from "./AccountDetailsStep"; +import { AwarenessStep } from "./AwarenessStep"; +import { PrimaryUseStep } from "./PrimaryUseStep"; +import { SlackStep } from "./SlackStep"; +import { SurveyUserProvider } from "./SurveyUserContext"; export function SurveyWizard() { const { data, isPending, isError } = useCurrentUser({ throwOnError: true }); @@ -18,11 +19,12 @@ export function SurveyWizard() { return ( <> - + {surveyStep === 1 && } {surveyStep === 2 && } {surveyStep === 3 && } - {surveyStep === 4 && ( + {surveyStep === 4 && } + {surveyStep === 5 && ( ; diff --git a/src/components/artifacts/artifact-node-sheet/DetailCards.tsx b/src/components/artifacts/artifact-node-sheet/DetailCards.tsx index 9d4fe2953..2f107bd72 100644 --- a/src/components/artifacts/artifact-node-sheet/DetailCards.tsx +++ b/src/components/artifacts/artifact-node-sheet/DetailCards.tsx @@ -175,7 +175,9 @@ export function DataCard({ artifactVersionId }: Props) { > - {artifactVersionData.body?.data_type.attribute} + + {artifactVersionData.body?.data_type.attribute} + {artifactVersionData.body?.data_type.module}. {artifactVersionData.body?.data_type.attribute}{" "} diff --git a/src/components/survey/form-schemas.ts b/src/components/survey/form-schemas.ts index b64b62d1c..ced63ffdd 100644 --- a/src/components/survey/form-schemas.ts +++ b/src/components/survey/form-schemas.ts @@ -25,7 +25,7 @@ export type PrimaryUseFormType = z.infer; export const AwarenessFormSchema = z .object({ - channels: z.string().array().min(1), + channels: z.string().array(), other: z.boolean(), otherVal: z.string().optional() }) @@ -33,7 +33,7 @@ export const AwarenessFormSchema = z if (data.other) { return data.otherVal !== ""; } - return true; + return data.channels.length > 0; }); export type AwarenessFormType = z.infer;