From 1f0800cafb448f519f52e4606015957d2a6e6087 Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Wed, 23 Apr 2025 22:23:30 +0200 Subject: [PATCH 1/8] Restructure quick-start with stepper --- docs/getting-started/quick-start.mdx | 426 +++++++++++++++++++++++++++ docs/quick-start.mdx | 375 ----------------------- sidebars.js | 2 +- 3 files changed, 427 insertions(+), 376 deletions(-) create mode 100644 docs/getting-started/quick-start.mdx delete mode 100644 docs/quick-start.mdx diff --git a/docs/getting-started/quick-start.mdx b/docs/getting-started/quick-start.mdx new file mode 100644 index 00000000000..9981a926ed2 --- /dev/null +++ b/docs/getting-started/quick-start.mdx @@ -0,0 +1,426 @@ +--- +slug: /getting-started/quick-start +sidebar_label: 'Quick Start' +sidebar_position: 1 +keywords: ['clickhouse', 'install', 'getting started', 'quick start'] +pagination_next: getting-started/index +title: 'Quick Start' +description: 'ClickHouse Quick Start guide' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import {VerticalStepper} from '@clickhouse/click-ui/bundled'; + +Welcome to ClickHouse! + +In this quick-start tutorial we'll get you set-up in 8 +easy steps. You'll download an appropriate binary for your OS, +learn to run ClickHouse server, and use the ClickHouse client to create a table, +insert data into it and run a query to select that data. + +All you'll need to follow along is your laptop, and curl or another command-line +HTTP client to fetch the ClickHouse binary. + +Let's get started, shall we? + + + +
+ ## Download the binary + + ClickHouse runs natively on Linux, FreeBSD and macOS, and runs on Windows via + the [WSL](https://learn.microsoft.com/en-us/windows/wsl/about). The simplest way to download ClickHouse locally is to run the + following `curl` command. It determines if your operating system is supported, + then downloads an appropriate ClickHouse binary. + + :::note + We recommend running the command below from a new and empty subdirectory as + some configuration files will be created in the directory the binary is located + in the first time ClickHouse server is run. + ::: + + You should see: + + ``` + Successfully downloaded the ClickHouse binary, you can run it as: + ./clickhouse + + You can also install it: + sudo ./clickhouse install + ``` + + At this stage, you can ignore the prompt to run the `install` command. + + :::note + For Mac users: If you are getting errors that the developer of the binary cannot + be verified, please see ["Fix the Developer Verification Error in MacOS"](https://clickhouse.com/docs/knowledgebase/fix-developer-verification-error-in-macos). + ::: +
+
+ +
+ ## Start the server + + Run the following command to start the ClickHouse server: + + ```bash + ./clickhouse server + ``` + + You should see the terminal fill up with logging. This is expected. In ClickHouse + the [default logging level](https://clickhouse.com/docs/knowledgebase/why_default_logging_verbose) + is set to `trace` rather than `warning`. +
+
+ +
+ ## Start the client + + Use `clickhouse-client` to connect to your ClickHouse service. Open a new + terminal, change directories to where your `clickhouse` binary is saved, and + run the following command: + + ```bash + ./clickhouse client + ``` + + You should see a smiling face as it connects to your service running on localhost: + + ```response + my-host :) + ``` +
+
+ +
+ ## Create a table + + Use `CREATE TABLE` to define a new table. Typical SQL DDL commands work in + ClickHouse with one addition - tables in ClickHouse require + an `ENGINE` clause. Use [`MergeTree`](/engines/table-engines/mergetree-family/mergetree) + to take advantage of the performance benefits of ClickHouse: + + ```sql + CREATE TABLE my_first_table + ( + user_id UInt32, + message String, + timestamp DateTime, + metric Float32 + ) + ENGINE = MergeTree + PRIMARY KEY (user_id, timestamp) + ``` +
+
+ +
+ ## Insert data + + You can use the familiar `INSERT INTO TABLE` command with ClickHouse, but it is + important to understand that each insert into a `MergeTree` table causes what we + call a **part** in ClickHouse to be created in storage. These parts later get + merged in the background by ClickHouse. + + In ClickHouse, we try to bulk insert lots of rows at a time + (tens of thousands or even millions at once) to minimize the number of [**parts**](/parts) + that need to get merged in the background process. + + In this guide, we won't worry about that just yet. Run the following command to + insert a few rows of data into your table: + + ```sql + INSERT INTO my_first_table (user_id, message, timestamp, metric) VALUES + (101, 'Hello, ClickHouse!', now(), -1.0 ), + (102, 'Insert a lot of rows per batch', yesterday(), 1.41421 ), + (102, 'Sort your data based on your commonly-used queries', today(), 2.718 ), + (101, 'Granules are the smallest chunks of data read', now() + 5, 3.14159 ) + ``` +
+
+ +
+ ## Query your new table + + You can write a `SELECT` query just like you would with any SQL database: + + ```sql + SELECT * + FROM my_first_table + ORDER BY timestamp + ``` + Notice the response comes back in a nice table format: + + ```text + ┌─user_id─┬─message────────────────────────────────────────────┬───────────timestamp─┬──metric─┐ + │ 102 │ Insert a lot of rows per batch │ 2022-03-21 00:00:00 │ 1.41421 │ + │ 102 │ Sort your data based on your commonly-used queries │ 2022-03-22 00:00:00 │ 2.718 │ + │ 101 │ Hello, ClickHouse! │ 2022-03-22 14:04:09 │ -1 │ + │ 101 │ Granules are the smallest chunks of data read │ 2022-03-22 14:04:14 │ 3.14159 │ + └─────────┴────────────────────────────────────────────────────┴─────────────────────┴─────────┘ + + 4 rows in set. Elapsed: 0.008 sec. + ``` +
+
+ +
+ ## Insert your own data + + The next step is to get your own data into ClickHouse. We have lots of [table functions](/sql-reference/table-functions/index.md) + and [integrations](/integrations) for ingesting data. We have some examples in the tabs + below, or you can check out our [Integrations](/integrations) page for a long list of + technologies that integrate with ClickHouse. + + + + + Use the [`s3` table function](/sql-reference/table-functions/s3.md) to + read files from S3. It's a table function - meaning that the result is a table + that can be: + + 1. used as the source of a `SELECT` query (allowing you to run ad-hoc queries and + leave your data in S3), or... + 2. insert the resulting table into a `MergeTree` table (when you are ready to + move your data into ClickHouse) + + An ad-hoc query looks like: + + ```sql + SELECT + passenger_count, + avg(toFloat32(total_amount)) + FROM s3( + 'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_0.gz', + 'TabSeparatedWithNames' + ) + GROUP BY passenger_count + ORDER BY passenger_count; + ``` + + Moving the data into a ClickHouse table looks like the following, where + `nyc_taxi` is a `MergeTree` table: + + ```sql + INSERT INTO nyc_taxi + SELECT * FROM s3( + 'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_0.gz', + 'TabSeparatedWithNames' + ) + SETTINGS input_format_allow_errors_num=25000; + ``` + + View our [collection of AWS S3 documentation pages](/integrations/data-ingestion/s3/index.md) for lots more details and examples of using S3 with ClickHouse. +
+
+ + + The [`s3` table function](/sql-reference/table-functions/s3.md) used for + reading data in AWS S3 also works on files in Google Cloud Storage. + + For example: + + ```sql + SELECT + * + FROM s3( + 'https://storage.googleapis.com/my-bucket/trips.parquet', + 'MY_GCS_HMAC_KEY', + 'MY_GCS_HMAC_SECRET_KEY', + 'Parquet' + ) + LIMIT 1000 + ``` + + Find more details on the [`s3` table function page](/sql-reference/table-functions/s3.md). +
+
+ + + The [`url` table function](/sql-reference/table-functions/url) reads + files accessible from the web: + + ```sql + --By default, ClickHouse prevents redirects to protect from SSRF attacks. + --The URL below requires a redirect, so we must set max_http_get_redirects > 0. + SET max_http_get_redirects=10; + + SELECT * + FROM url( + 'http://prod2.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-complete.csv', + 'CSV' + ); + ``` + + Find more details on the [`url` table function page](/sql-reference/table-functions/url). +
+
+ + + Use the [`file` table engine](/sql-reference/table-functions/file) to + read a local file. For simplicity, copy the file to the `user_files` directory + (which is found in the directory where you downloaded the ClickHouse binary). + + ```sql + DESCRIBE TABLE file('comments.tsv') + + Query id: 8ca9b2f9-65a2-4982-954a-890de710a336 + + ┌─name──────┬─type────────────────────┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┬─ttl_expression─┐ + │ id │ Nullable(Int64) │ │ │ │ │ │ + │ type │ Nullable(String) │ │ │ │ │ │ + │ author │ Nullable(String) │ │ │ │ │ │ + │ timestamp │ Nullable(DateTime64(9)) │ │ │ │ │ │ + │ comment │ Nullable(String) │ │ │ │ │ │ + │ children │ Array(Nullable(Int64)) │ │ │ │ │ │ + └───────────┴─────────────────────────┴──────────────┴────────────────────┴─────────┴──────────────────┴────────────────┘ + ``` + + Notice ClickHouse infers the names and data types of your columns by analyzing a + large batch of rows. If ClickHouse can not determine the storage type from the + filename, you can specify it as the second argument: + + ```sql + SELECT count() + FROM file( + 'comments.tsv', + 'TabSeparatedWithNames' + ) + ``` + + View the [`file` table function](/sql-reference/table-functions/file) + docs page for more details. +
+
+ + + Use the [`postgresql` table function](/sql-reference/table-functions/postgresql) + to read data from a table in PostgreSQL: + + ```sql + SELECT * + FROM + postgresql( + 'localhost:5432', + 'my_database', + 'my_table', + 'postgresql_user', + 'password') + ; + ``` + + View the [`postgresql` table function](/sql-reference/table-functions/postgresql) + docs page for more details. +
+
+ + + Use the [`mysql` table function](/sql-reference/table-functions/mysql) + to read data from a table in MySQL: + + ```sql + SELECT * + FROM + mysql( + 'localhost:3306', + 'my_database', + 'my_table', + 'postgresql_user', + 'password') + ; + ``` + + View the [`mysql` table function](/sql-reference/table-functions/mysql) + docs page for more details. +
+
+ + + ClickHouse can read data from any ODBC or JDBC data source: + + ```sql + SELECT * + FROM + odbc( + 'DSN=mysqlconn', + 'my_database', + 'my_table' + ); + ``` + + View the [`odbc` table function](/sql-reference/table-functions/odbc) + and the [`jdbc` table function](/sql-reference/table-functions/jdbc) docs + pages for more details. +
+
+ + + Message queues can stream data into ClickHouse using the corresponding table + engine, including: + + - **Kafka**: integrate with Kafka using the [`Kafka` table engine](/engines/table-engines/integrations/kafka) + - **Amazon MSK**: integrate with [Amazon Managed Streaming for Apache Kafka (MSK)](/integrations/kafka/cloud/amazon-msk/) + - **RabbitMQ**: integrate with RabbitMQ using the [`RabbitMQ` table engine](/engines/table-engines/integrations/rabbitmq) +
+
+ + + ClickHouse has table functions to read data from the following sources: + + - **Hadoop**: integrate with Apache Hadoop using the [`hdfs` table function](/sql-reference/table-functions/hdfs) + - **Hudi**: read from existing Apache Hudi tables in S3 using the [`hudi` table function](/sql-reference/table-functions/hudi) + - **Iceberg**: read from existing Apache Iceberg tables in S3 using the [`iceberg` table function](/sql-reference/table-functions/iceberg) + - **DeltaLake**: read from existing Delta Lake tables in S3 using the [`deltaLake` table function](/sql-reference/table-functions/deltalake) +
+
+ + + Check out our [long list of ClickHouse integrations](/integrations) to find how to connect your existing frameworks and data sources to ClickHouse. +
+
+
+
+
+ +
+ ## Next steps + + - Check out our [Core Concepts](/managing-data/core-concepts) section to learn some of the fundamentals of how ClickHouse works under the hood. + - Check out the [Advanced Tutorial](tutorial.md) which takes a much deeper dive into the key concepts and capabilities of ClickHouse. + - Continue your learning by taking our free on-demand training courses at the [ClickHouse Academy](https://learn.clickhouse.com/visitor_class_catalog). + - We have a list of [example datasets](/getting-started/example-datasets/) with instructions on how to insert them. + - If your data is coming from an external source, view our [collection of integration guides](/integrations/) for connecting to message queues, databases, pipelines and more. + - If you are using a UI/BI visualization tool, view the [user guides for connecting a UI to ClickHouse](/integrations/data-visualization/). + - The user guide on [primary keys](/guides/best-practices/sparse-primary-indexes.md) is everything you need to know about primary keys and how to define them. +
+
+
diff --git a/docs/quick-start.mdx b/docs/quick-start.mdx deleted file mode 100644 index f5da552e1db..00000000000 --- a/docs/quick-start.mdx +++ /dev/null @@ -1,375 +0,0 @@ ---- -slug: /getting-started/quick-start -sidebar_label: 'Quick Start' -sidebar_position: 1 -keywords: ['clickhouse', 'install', 'getting started', 'quick start'] -pagination_next: getting-started/index -title: 'Quick Start' -description: 'ClickHouse Quick Start guide' ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -## Overview - -Get set up quickly with ClickHouse. Download an appropriate binary for your OS, -learn to run ClickHouse server, and create a table, insert data into it, and -query your table using ClickHouse client. - -### Prerequisites - -You'll need curl or another command-line HTTP client to fetch the ClickHouse -binary. - -## Download the binary - -ClickHouse runs natively on Linux, FreeBSD and macOS, and runs on Windows via -the [WSL](https://learn.microsoft.com/en-us/windows/wsl/about). The simplest way to download ClickHouse locally is to run the -following `curl` command. It determines if your operating system is supported, -then downloads an appropriate ClickHouse binary. - -:::note -We recommend running the command below from a new and empty subdirectory as -some configuration files will be created in the directory the binary is located -in the first time ClickHouse server is run. -::: - -```bash -curl https://clickhouse.com/ | sh -``` - -You should see: - -``` -Successfully downloaded the ClickHouse binary, you can run it as: - ./clickhouse - -You can also install it: -sudo ./clickhouse install -``` - -At this stage, you can ignore the prompt to run the `install` command. - -:::note -For Mac users: If you are getting errors that the developer of the binary cannot -be verified, please see ["Fix the Developer Verification Error in MacOS"](https://clickhouse.com/docs/knowledgebase/fix-developer-verification-error-in-macos). -::: - - -## Start the server - -Run the following command to start the ClickHouse server: - -```bash -./clickhouse server -``` - -You should see the terminal fill up with logging. This is expected. In ClickHouse -the [default logging level](https://clickhouse.com/docs/knowledgebase/why_default_logging_verbose) -is set to `trace` rather than `warning`. - -## Start the client - -Use `clickhouse-client` to connect to your ClickHouse service. Open a new -terminal, change directories to where your `clickhouse` binary is saved, and -run the following command: - -```bash -./clickhouse client -``` - -You should see a smiling face as it connects to your service running on localhost: - -```response -my-host :) -``` - -## Create a table - -Use `CREATE TABLE` to define a new table. Typical SQL DDL commands work in -ClickHouse with one addition - tables in ClickHouse require -an `ENGINE` clause. Use [`MergeTree`](/engines/table-engines/mergetree-family/mergetree) -to take advantage of the performance benefits of ClickHouse: - -```sql -CREATE TABLE my_first_table -( - user_id UInt32, - message String, - timestamp DateTime, - metric Float32 -) -ENGINE = MergeTree -PRIMARY KEY (user_id, timestamp) -``` - -## Insert data - -You can use the familiar `INSERT INTO TABLE` command with ClickHouse, but it is -important to understand that each insert into a `MergeTree` table causes what we -call a **part** in ClickHouse to be created in storage. These parts later get -merged in the background by ClickHouse. - -In ClickHouse, we try to bulk insert lots of rows at a time -(tens of thousands or even millions at once) to minimize the number of [**parts**](/parts) -that need to get merged in the background process. - -In this guide, we won't worry about that just yet. Run the following command to -insert a few rows of data into your table: - -```sql -INSERT INTO my_first_table (user_id, message, timestamp, metric) VALUES - (101, 'Hello, ClickHouse!', now(), -1.0 ), - (102, 'Insert a lot of rows per batch', yesterday(), 1.41421 ), - (102, 'Sort your data based on your commonly-used queries', today(), 2.718 ), - (101, 'Granules are the smallest chunks of data read', now() + 5, 3.14159 ) -``` - -## Query your new table - -You can write a `SELECT` query just like you would with any SQL database: - -```sql -SELECT * -FROM my_first_table -ORDER BY timestamp -``` -Notice the response comes back in a nice table format: - -```text -┌─user_id─┬─message────────────────────────────────────────────┬───────────timestamp─┬──metric─┐ -│ 102 │ Insert a lot of rows per batch │ 2022-03-21 00:00:00 │ 1.41421 │ -│ 102 │ Sort your data based on your commonly-used queries │ 2022-03-22 00:00:00 │ 2.718 │ -│ 101 │ Hello, ClickHouse! │ 2022-03-22 14:04:09 │ -1 │ -│ 101 │ Granules are the smallest chunks of data read │ 2022-03-22 14:04:14 │ 3.14159 │ -└─────────┴────────────────────────────────────────────────────┴─────────────────────┴─────────┘ - -4 rows in set. Elapsed: 0.008 sec. -``` - -## Insert your own data - -The next step is to get your own data into ClickHouse. We have lots of [table functions](/sql-reference/table-functions/index.md) -and [integrations](/integrations) for ingesting data. We have some examples in the tabs -below, or you can check out our [Integrations](/integrations) page for a long list of -technologies that integrate with ClickHouse. - - - - -Use the [`s3` table function](/sql-reference/table-functions/s3.md) to -read files from S3. It's a table function - meaning that the result is a table -that can be: - -1. used as the source of a `SELECT` query (allowing you to run ad-hoc queries and - leave your data in S3), or... -2. insert the resulting table into a `MergeTree` table (when you are ready to - move your data into ClickHouse) - -An ad-hoc query looks like: - -```sql -SELECT - passenger_count, - avg(toFloat32(total_amount)) -FROM s3( - 'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_0.gz', - 'TabSeparatedWithNames' -) -GROUP BY passenger_count -ORDER BY passenger_count; -``` - -Moving the data into a ClickHouse table looks like the following, where -`nyc_taxi` is a `MergeTree` table: - -```sql -INSERT INTO nyc_taxi - SELECT * FROM s3( - 'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_0.gz', - 'TabSeparatedWithNames' -) -SETTINGS input_format_allow_errors_num=25000; -``` - -View our [collection of AWS S3 documentation pages](/integrations/data-ingestion/s3/index.md) for lots more details and examples of using S3 with ClickHouse. -
-
- - -The [`s3` table function](/sql-reference/table-functions/s3.md) used for -reading data in AWS S3 also works on files in Google Cloud Storage. - -For example: - -```sql -SELECT - * -FROM s3( - 'https://storage.googleapis.com/my-bucket/trips.parquet', - 'MY_GCS_HMAC_KEY', - 'MY_GCS_HMAC_SECRET_KEY', - 'Parquet' -) -LIMIT 1000 -``` - -Find more details on the [`s3` table function page](/sql-reference/table-functions/s3.md). -
-
- - -The [`url` table function](/sql-reference/table-functions/url) reads -files accessible from the web: - -```sql ---By default, ClickHouse prevents redirects to protect from SSRF attacks. ---The URL below requires a redirect, so we must set max_http_get_redirects > 0. -SET max_http_get_redirects=10; - -SELECT * -FROM url( - 'http://prod2.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-complete.csv', - 'CSV' - ); -``` - -Find more details on the [`url` table function page](/sql-reference/table-functions/url). -
-
- - -Use the [`file` table engine](/sql-reference/table-functions/file) to -read a local file. For simplicity, copy the file to the `user_files` directory -(which is found in the directory where you downloaded the ClickHouse binary). - -```sql -DESCRIBE TABLE file('comments.tsv') - -Query id: 8ca9b2f9-65a2-4982-954a-890de710a336 - -┌─name──────┬─type────────────────────┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┬─ttl_expression─┐ -│ id │ Nullable(Int64) │ │ │ │ │ │ -│ type │ Nullable(String) │ │ │ │ │ │ -│ author │ Nullable(String) │ │ │ │ │ │ -│ timestamp │ Nullable(DateTime64(9)) │ │ │ │ │ │ -│ comment │ Nullable(String) │ │ │ │ │ │ -│ children │ Array(Nullable(Int64)) │ │ │ │ │ │ -└───────────┴─────────────────────────┴──────────────┴────────────────────┴─────────┴──────────────────┴────────────────┘ -``` - -Notice ClickHouse infers the names and data types of your columns by analyzing a -large batch of rows. If ClickHouse can not determine the storage type from the -filename, you can specify it as the second argument: - -```sql -SELECT count() -FROM file( - 'comments.tsv', - 'TabSeparatedWithNames' -) -``` - -View the [`file` table function](/sql-reference/table-functions/file) -docs page for more details. -
-
- - -Use the [`postgresql` table function](/sql-reference/table-functions/postgresql) -to read data from a table in PostgreSQL: - -```sql -SELECT * -FROM - postgresql( - 'localhost:5432', - 'my_database', - 'my_table', - 'postgresql_user', - 'password') -; -``` - -View the [`postgresql` table function](/sql-reference/table-functions/postgresql) -docs page for more details. -
-
- - -Use the [`mysql` table function](/sql-reference/table-functions/mysql) -to read data from a table in MySQL: - -```sql -SELECT * -FROM - mysql( - 'localhost:3306', - 'my_database', - 'my_table', - 'postgresql_user', - 'password') -; -``` - -View the [`mysql` table function](/sql-reference/table-functions/mysql) -docs page for more details. -
-
- - -ClickHouse can read data from any ODBC or JDBC data source: - -```sql -SELECT * -FROM - odbc( - 'DSN=mysqlconn', - 'my_database', - 'my_table' - ); -``` - -View the [`odbc` table function](/sql-reference/table-functions/odbc) -and the [`jdbc` table function](/sql-reference/table-functions/jdbc) docs -pages for more details. -
-
- - -Message queues can stream data into ClickHouse using the corresponding table -engine, including: - -- **Kafka**: integrate with Kafka using the [`Kafka` table engine](/engines/table-engines/integrations/kafka) -- **Amazon MSK**: integrate with [Amazon Managed Streaming for Apache Kafka (MSK)](/integrations/kafka/cloud/amazon-msk/) -- **RabbitMQ**: integrate with RabbitMQ using the [`RabbitMQ` table engine](/engines/table-engines/integrations/rabbitmq) -
-
- - -ClickHouse has table functions to read data from the following sources: - -- **Hadoop**: integrate with Apache Hadoop using the [`hdfs` table function](/sql-reference/table-functions/hdfs) -- **Hudi**: read from existing Apache Hudi tables in S3 using the [`hudi` table function](/sql-reference/table-functions/hudi) -- **Iceberg**: read from existing Apache Iceberg tables in S3 using the [`iceberg` table function](/sql-reference/table-functions/iceberg) -- **DeltaLake**: read from existing Delta Lake tables in S3 using the [`deltaLake` table function](/sql-reference/table-functions/deltalake) -
-
- - -Check out our [long list of ClickHouse integrations](/integrations) to find how to connect your existing frameworks and data sources to ClickHouse. -
-
-
- -## Next steps - -- Check out our [Core Concepts](/managing-data/core-concepts) section to learn some of the fundamentals of how ClickHouse works under the hood. -- Check out the [Advanced Tutorial](tutorial.md) which takes a much deeper dive into the key concepts and capabilities of ClickHouse. -- Continue your learning by taking our free on-demand training courses at the [ClickHouse Academy](https://learn.clickhouse.com/visitor_class_catalog). -- We have a list of [example datasets](/getting-started/example-datasets/) with instructions on how to insert them. -- If your data is coming from an external source, view our [collection of integration guides](/integrations/) for connecting to message queues, databases, pipelines and more. -- If you are using a UI/BI visualization tool, view the [user guides for connecting a UI to ClickHouse](/integrations/data-visualization/). -- The user guide on [primary keys](/guides/best-practices/sparse-primary-indexes.md) is everything you need to know about primary keys and how to define them. diff --git a/sidebars.js b/sidebars.js index 232758d2a0e..48ba7dd8749 100644 --- a/sidebars.js +++ b/sidebars.js @@ -14,7 +14,7 @@ const sidebars = { link: { type: "doc", id: "introduction-index" }, items: [ "intro", - "quick-start", + "getting-started/quick-start", "tutorial", "getting-started/install", "deployment-modes", From 32d5aa971b5eed0766a60657a88be33b2e6c2e64 Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Fri, 25 Apr 2025 08:03:28 +0200 Subject: [PATCH 2/8] Add VerticalStepper component with custom markdown syntax --- docs/getting-started/quick-start.mdx | 753 ++++++++---------- docusaurus.config.en.js | 12 +- package.json | 3 +- plugins/remark-custom-blocks.js | 194 +++++ .../VerticalStepper/VerticalStepper.jsx | 88 ++ src/css/default.scss | 4 +- src/theme/MDXComponents.js | 10 + 7 files changed, 659 insertions(+), 405 deletions(-) create mode 100644 plugins/remark-custom-blocks.js create mode 100644 src/components/VerticalStepper/VerticalStepper.jsx create mode 100644 src/theme/MDXComponents.js diff --git a/docs/getting-started/quick-start.mdx b/docs/getting-started/quick-start.mdx index 9981a926ed2..249b117bb9a 100644 --- a/docs/getting-started/quick-start.mdx +++ b/docs/getting-started/quick-start.mdx @@ -25,402 +25,357 @@ HTTP client to fetch the ClickHouse binary. Let's get started, shall we? - - -
- ## Download the binary - - ClickHouse runs natively on Linux, FreeBSD and macOS, and runs on Windows via - the [WSL](https://learn.microsoft.com/en-us/windows/wsl/about). The simplest way to download ClickHouse locally is to run the - following `curl` command. It determines if your operating system is supported, - then downloads an appropriate ClickHouse binary. - - :::note - We recommend running the command below from a new and empty subdirectory as - some configuration files will be created in the directory the binary is located - in the first time ClickHouse server is run. - ::: - - You should see: - - ``` - Successfully downloaded the ClickHouse binary, you can run it as: - ./clickhouse - - You can also install it: - sudo ./clickhouse install - ``` - - At this stage, you can ignore the prompt to run the `install` command. - - :::note - For Mac users: If you are getting errors that the developer of the binary cannot - be verified, please see ["Fix the Developer Verification Error in MacOS"](https://clickhouse.com/docs/knowledgebase/fix-developer-verification-error-in-macos). - ::: -
-
- -
- ## Start the server - - Run the following command to start the ClickHouse server: - - ```bash - ./clickhouse server - ``` - - You should see the terminal fill up with logging. This is expected. In ClickHouse - the [default logging level](https://clickhouse.com/docs/knowledgebase/why_default_logging_verbose) - is set to `trace` rather than `warning`. -
-
- -
- ## Start the client - - Use `clickhouse-client` to connect to your ClickHouse service. Open a new - terminal, change directories to where your `clickhouse` binary is saved, and - run the following command: - - ```bash - ./clickhouse client - ``` - - You should see a smiling face as it connects to your service running on localhost: - - ```response - my-host :) - ``` -
-
- -
- ## Create a table - - Use `CREATE TABLE` to define a new table. Typical SQL DDL commands work in - ClickHouse with one addition - tables in ClickHouse require - an `ENGINE` clause. Use [`MergeTree`](/engines/table-engines/mergetree-family/mergetree) - to take advantage of the performance benefits of ClickHouse: - - ```sql - CREATE TABLE my_first_table - ( - user_id UInt32, - message String, - timestamp DateTime, - metric Float32 - ) - ENGINE = MergeTree - PRIMARY KEY (user_id, timestamp) - ``` -
-
- -
- ## Insert data - - You can use the familiar `INSERT INTO TABLE` command with ClickHouse, but it is - important to understand that each insert into a `MergeTree` table causes what we - call a **part** in ClickHouse to be created in storage. These parts later get - merged in the background by ClickHouse. - - In ClickHouse, we try to bulk insert lots of rows at a time - (tens of thousands or even millions at once) to minimize the number of [**parts**](/parts) - that need to get merged in the background process. - - In this guide, we won't worry about that just yet. Run the following command to - insert a few rows of data into your table: - - ```sql - INSERT INTO my_first_table (user_id, message, timestamp, metric) VALUES - (101, 'Hello, ClickHouse!', now(), -1.0 ), - (102, 'Insert a lot of rows per batch', yesterday(), 1.41421 ), - (102, 'Sort your data based on your commonly-used queries', today(), 2.718 ), - (101, 'Granules are the smallest chunks of data read', now() + 5, 3.14159 ) - ``` -
-
- -
- ## Query your new table - - You can write a `SELECT` query just like you would with any SQL database: - - ```sql - SELECT * - FROM my_first_table - ORDER BY timestamp - ``` - Notice the response comes back in a nice table format: - - ```text - ┌─user_id─┬─message────────────────────────────────────────────┬───────────timestamp─┬──metric─┐ - │ 102 │ Insert a lot of rows per batch │ 2022-03-21 00:00:00 │ 1.41421 │ - │ 102 │ Sort your data based on your commonly-used queries │ 2022-03-22 00:00:00 │ 2.718 │ - │ 101 │ Hello, ClickHouse! │ 2022-03-22 14:04:09 │ -1 │ - │ 101 │ Granules are the smallest chunks of data read │ 2022-03-22 14:04:14 │ 3.14159 │ - └─────────┴────────────────────────────────────────────────────┴─────────────────────┴─────────┘ - - 4 rows in set. Elapsed: 0.008 sec. - ``` -
-
- -
- ## Insert your own data - - The next step is to get your own data into ClickHouse. We have lots of [table functions](/sql-reference/table-functions/index.md) - and [integrations](/integrations) for ingesting data. We have some examples in the tabs - below, or you can check out our [Integrations](/integrations) page for a long list of - technologies that integrate with ClickHouse. - - - - - Use the [`s3` table function](/sql-reference/table-functions/s3.md) to - read files from S3. It's a table function - meaning that the result is a table - that can be: - - 1. used as the source of a `SELECT` query (allowing you to run ad-hoc queries and - leave your data in S3), or... - 2. insert the resulting table into a `MergeTree` table (when you are ready to - move your data into ClickHouse) - - An ad-hoc query looks like: - - ```sql - SELECT - passenger_count, - avg(toFloat32(total_amount)) - FROM s3( - 'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_0.gz', - 'TabSeparatedWithNames' - ) - GROUP BY passenger_count - ORDER BY passenger_count; - ``` - - Moving the data into a ClickHouse table looks like the following, where - `nyc_taxi` is a `MergeTree` table: - - ```sql - INSERT INTO nyc_taxi - SELECT * FROM s3( - 'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_0.gz', - 'TabSeparatedWithNames' - ) - SETTINGS input_format_allow_errors_num=25000; - ``` - - View our [collection of AWS S3 documentation pages](/integrations/data-ingestion/s3/index.md) for lots more details and examples of using S3 with ClickHouse. -
-
- - - The [`s3` table function](/sql-reference/table-functions/s3.md) used for - reading data in AWS S3 also works on files in Google Cloud Storage. - - For example: - - ```sql - SELECT - * - FROM s3( - 'https://storage.googleapis.com/my-bucket/trips.parquet', - 'MY_GCS_HMAC_KEY', - 'MY_GCS_HMAC_SECRET_KEY', - 'Parquet' - ) - LIMIT 1000 - ``` - - Find more details on the [`s3` table function page](/sql-reference/table-functions/s3.md). -
-
- - - The [`url` table function](/sql-reference/table-functions/url) reads - files accessible from the web: - - ```sql - --By default, ClickHouse prevents redirects to protect from SSRF attacks. - --The URL below requires a redirect, so we must set max_http_get_redirects > 0. - SET max_http_get_redirects=10; - - SELECT * - FROM url( - 'http://prod2.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-complete.csv', - 'CSV' - ); - ``` - - Find more details on the [`url` table function page](/sql-reference/table-functions/url). -
-
- - - Use the [`file` table engine](/sql-reference/table-functions/file) to - read a local file. For simplicity, copy the file to the `user_files` directory - (which is found in the directory where you downloaded the ClickHouse binary). - - ```sql - DESCRIBE TABLE file('comments.tsv') - - Query id: 8ca9b2f9-65a2-4982-954a-890de710a336 - - ┌─name──────┬─type────────────────────┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┬─ttl_expression─┐ - │ id │ Nullable(Int64) │ │ │ │ │ │ - │ type │ Nullable(String) │ │ │ │ │ │ - │ author │ Nullable(String) │ │ │ │ │ │ - │ timestamp │ Nullable(DateTime64(9)) │ │ │ │ │ │ - │ comment │ Nullable(String) │ │ │ │ │ │ - │ children │ Array(Nullable(Int64)) │ │ │ │ │ │ - └───────────┴─────────────────────────┴──────────────┴────────────────────┴─────────┴──────────────────┴────────────────┘ - ``` - - Notice ClickHouse infers the names and data types of your columns by analyzing a - large batch of rows. If ClickHouse can not determine the storage type from the - filename, you can specify it as the second argument: - - ```sql - SELECT count() - FROM file( - 'comments.tsv', - 'TabSeparatedWithNames' - ) - ``` - - View the [`file` table function](/sql-reference/table-functions/file) - docs page for more details. -
-
- - - Use the [`postgresql` table function](/sql-reference/table-functions/postgresql) - to read data from a table in PostgreSQL: - - ```sql - SELECT * - FROM - postgresql( - 'localhost:5432', - 'my_database', - 'my_table', - 'postgresql_user', - 'password') - ; - ``` - - View the [`postgresql` table function](/sql-reference/table-functions/postgresql) - docs page for more details. -
-
- - - Use the [`mysql` table function](/sql-reference/table-functions/mysql) - to read data from a table in MySQL: - - ```sql - SELECT * - FROM - mysql( - 'localhost:3306', - 'my_database', - 'my_table', - 'postgresql_user', - 'password') - ; - ``` - - View the [`mysql` table function](/sql-reference/table-functions/mysql) - docs page for more details. -
-
- - - ClickHouse can read data from any ODBC or JDBC data source: - - ```sql - SELECT * - FROM - odbc( - 'DSN=mysqlconn', - 'my_database', - 'my_table' - ); - ``` - - View the [`odbc` table function](/sql-reference/table-functions/odbc) - and the [`jdbc` table function](/sql-reference/table-functions/jdbc) docs - pages for more details. -
-
- - - Message queues can stream data into ClickHouse using the corresponding table - engine, including: - - - **Kafka**: integrate with Kafka using the [`Kafka` table engine](/engines/table-engines/integrations/kafka) - - **Amazon MSK**: integrate with [Amazon Managed Streaming for Apache Kafka (MSK)](/integrations/kafka/cloud/amazon-msk/) - - **RabbitMQ**: integrate with RabbitMQ using the [`RabbitMQ` table engine](/engines/table-engines/integrations/rabbitmq) -
-
- - - ClickHouse has table functions to read data from the following sources: - - - **Hadoop**: integrate with Apache Hadoop using the [`hdfs` table function](/sql-reference/table-functions/hdfs) - - **Hudi**: read from existing Apache Hudi tables in S3 using the [`hudi` table function](/sql-reference/table-functions/hudi) - - **Iceberg**: read from existing Apache Iceberg tables in S3 using the [`iceberg` table function](/sql-reference/table-functions/iceberg) - - **DeltaLake**: read from existing Delta Lake tables in S3 using the [`deltaLake` table function](/sql-reference/table-functions/deltalake) -
-
- - - Check out our [long list of ClickHouse integrations](/integrations) to find how to connect your existing frameworks and data sources to ClickHouse. -
-
-
-
-
- -
- ## Next steps - - - Check out our [Core Concepts](/managing-data/core-concepts) section to learn some of the fundamentals of how ClickHouse works under the hood. - - Check out the [Advanced Tutorial](tutorial.md) which takes a much deeper dive into the key concepts and capabilities of ClickHouse. - - Continue your learning by taking our free on-demand training courses at the [ClickHouse Academy](https://learn.clickhouse.com/visitor_class_catalog). - - We have a list of [example datasets](/getting-started/example-datasets/) with instructions on how to insert them. - - If your data is coming from an external source, view our [collection of integration guides](/integrations/) for connecting to message queues, databases, pipelines and more. - - If you are using a UI/BI visualization tool, view the [user guides for connecting a UI to ClickHouse](/integrations/data-visualization/). - - The user guide on [primary keys](/guides/best-practices/sparse-primary-indexes.md) is everything you need to know about primary keys and how to define them. -
-
-
+ + +## Download the binary + +ClickHouse runs natively on Linux, FreeBSD and macOS, and runs on Windows via +the [WSL](https://learn.microsoft.com/en-us/windows/wsl/about). The simplest way to download ClickHouse locally is to run the +following `curl` command. It determines if your operating system is supported, +then downloads an appropriate ClickHouse binary. + +:::note +We recommend running the command below from a new and empty subdirectory as +some configuration files will be created in the directory the binary is located +in the first time ClickHouse server is run. +::: + +```bash +curl https://clickhouse.com/ | sh +``` + +You should see: + +``` +Successfully downloaded the ClickHouse binary, you can run it as: + ./clickhouse + +You can also install it: +sudo ./clickhouse install +``` + +At this stage, you can ignore the prompt to run the `install` command. + +:::note +For Mac users: If you are getting errors that the developer of the binary cannot +be verified, please see ["Fix the Developer Verification Error in MacOS"](https://clickhouse.com/docs/knowledgebase/fix-developer-verification-error-in-macos). +::: + + +## Start the server + +Run the following command to start the ClickHouse server: + +```bash +./clickhouse server +``` + +You should see the terminal fill up with logging. This is expected. In ClickHouse +the [default logging level](https://clickhouse.com/docs/knowledgebase/why_default_logging_verbose) +is set to `trace` rather than `warning`. + +## Start the client + +Use `clickhouse-client` to connect to your ClickHouse service. Open a new +terminal, change directories to where your `clickhouse` binary is saved, and +run the following command: + +```bash +./clickhouse client +``` + +You should see a smiling face as it connects to your service running on localhost: + +```response +my-host :) +``` + +## Create a table + +Use `CREATE TABLE` to define a new table. Typical SQL DDL commands work in +ClickHouse with one addition - tables in ClickHouse require +an `ENGINE` clause. Use [`MergeTree`](/engines/table-engines/mergetree-family/mergetree) +to take advantage of the performance benefits of ClickHouse: + +```sql +CREATE TABLE my_first_table +( + user_id UInt32, + message String, + timestamp DateTime, + metric Float32 +) +ENGINE = MergeTree +PRIMARY KEY (user_id, timestamp) +``` + +## Insert data + +You can use the familiar `INSERT INTO TABLE` command with ClickHouse, but it is +important to understand that each insert into a `MergeTree` table causes what we +call a **part** in ClickHouse to be created in storage. These parts later get +merged in the background by ClickHouse. + +In ClickHouse, we try to bulk insert lots of rows at a time +(tens of thousands or even millions at once) to minimize the number of [**parts**](/parts) +that need to get merged in the background process. + +In this guide, we won't worry about that just yet. Run the following command to +insert a few rows of data into your table: + +```sql +INSERT INTO my_first_table (user_id, message, timestamp, metric) VALUES + (101, 'Hello, ClickHouse!', now(), -1.0 ), + (102, 'Insert a lot of rows per batch', yesterday(), 1.41421 ), + (102, 'Sort your data based on your commonly-used queries', today(), 2.718 ), + (101, 'Granules are the smallest chunks of data read', now() + 5, 3.14159 ) +``` + +## Query your new table + +You can write a `SELECT` query just like you would with any SQL database: + +```sql +SELECT * +FROM my_first_table +ORDER BY timestamp +``` +Notice the response comes back in a nice table format: + +```text +┌─user_id─┬─message────────────────────────────────────────────┬───────────timestamp─┬──metric─┐ +│ 102 │ Insert a lot of rows per batch │ 2022-03-21 00:00:00 │ 1.41421 │ +│ 102 │ Sort your data based on your commonly-used queries │ 2022-03-22 00:00:00 │ 2.718 │ +│ 101 │ Hello, ClickHouse! │ 2022-03-22 14:04:09 │ -1 │ +│ 101 │ Granules are the smallest chunks of data read │ 2022-03-22 14:04:14 │ 3.14159 │ +└─────────┴────────────────────────────────────────────────────┴─────────────────────┴─────────┘ + +4 rows in set. Elapsed: 0.008 sec. +``` + +## Insert your own data + +The next step is to get your own data into ClickHouse. We have lots of [table functions](/sql-reference/table-functions/index.md) +and [integrations](/integrations) for ingesting data. We have some examples in the tabs +below, or you can check out our [Integrations](/integrations) page for a long list of +technologies that integrate with ClickHouse. + + + + + Use the [`s3` table function](/sql-reference/table-functions/s3.md) to + read files from S3. It's a table function - meaning that the result is a table + that can be: + + 1. used as the source of a `SELECT` query (allowing you to run ad-hoc queries and + leave your data in S3), or... + 2. insert the resulting table into a `MergeTree` table (when you are ready to + move your data into ClickHouse) + + An ad-hoc query looks like: + + ```sql + SELECT + passenger_count, + avg(toFloat32(total_amount)) + FROM s3( + 'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_0.gz', + 'TabSeparatedWithNames' + ) + GROUP BY passenger_count + ORDER BY passenger_count; + ``` + + Moving the data into a ClickHouse table looks like the following, where + `nyc_taxi` is a `MergeTree` table: + + ```sql + INSERT INTO nyc_taxi + SELECT * FROM s3( + 'https://datasets-documentation.s3.eu-west-3.amazonaws.com/nyc-taxi/trips_0.gz', + 'TabSeparatedWithNames' + ) + SETTINGS input_format_allow_errors_num=25000; + ``` + + View our [collection of AWS S3 documentation pages](/integrations/data-ingestion/s3/index.md) for lots more details and examples of using S3 with ClickHouse. +
+
+ + + The [`s3` table function](/sql-reference/table-functions/s3.md) used for + reading data in AWS S3 also works on files in Google Cloud Storage. + + For example: + + ```sql + SELECT + * + FROM s3( + 'https://storage.googleapis.com/my-bucket/trips.parquet', + 'MY_GCS_HMAC_KEY', + 'MY_GCS_HMAC_SECRET_KEY', + 'Parquet' + ) + LIMIT 1000 + ``` + + Find more details on the [`s3` table function page](/sql-reference/table-functions/s3.md). +
+
+ + + The [`url` table function](/sql-reference/table-functions/url) reads + files accessible from the web: + + ```sql + --By default, ClickHouse prevents redirects to protect from SSRF attacks. + --The URL below requires a redirect, so we must set max_http_get_redirects > 0. + SET max_http_get_redirects=10; + + SELECT * + FROM url( + 'http://prod2.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-complete.csv', + 'CSV' + ); + ``` + + Find more details on the [`url` table function page](/sql-reference/table-functions/url). +
+
+ + + Use the [`file` table engine](/sql-reference/table-functions/file) to + read a local file. For simplicity, copy the file to the `user_files` directory + (which is found in the directory where you downloaded the ClickHouse binary). + + ```sql + DESCRIBE TABLE file('comments.tsv') + + Query id: 8ca9b2f9-65a2-4982-954a-890de710a336 + + ┌─name──────┬─type────────────────────┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┬─ttl_expression─┐ + │ id │ Nullable(Int64) │ │ │ │ │ │ + │ type │ Nullable(String) │ │ │ │ │ │ + │ author │ Nullable(String) │ │ │ │ │ │ + │ timestamp │ Nullable(DateTime64(9)) │ │ │ │ │ │ + │ comment │ Nullable(String) │ │ │ │ │ │ + │ children │ Array(Nullable(Int64)) │ │ │ │ │ │ + └───────────┴─────────────────────────┴──────────────┴────────────────────┴─────────┴──────────────────┴────────────────┘ + ``` + + Notice ClickHouse infers the names and data types of your columns by analyzing a + large batch of rows. If ClickHouse can not determine the storage type from the + filename, you can specify it as the second argument: + + ```sql + SELECT count() + FROM file( + 'comments.tsv', + 'TabSeparatedWithNames' + ) + ``` + + View the [`file` table function](/sql-reference/table-functions/file) + docs page for more details. +
+
+ + + Use the [`postgresql` table function](/sql-reference/table-functions/postgresql) + to read data from a table in PostgreSQL: + + ```sql + SELECT * + FROM + postgresql( + 'localhost:5432', + 'my_database', + 'my_table', + 'postgresql_user', + 'password') + ; + ``` + + View the [`postgresql` table function](/sql-reference/table-functions/postgresql) + docs page for more details. +
+
+ + + Use the [`mysql` table function](/sql-reference/table-functions/mysql) + to read data from a table in MySQL: + + ```sql + SELECT * + FROM + mysql( + 'localhost:3306', + 'my_database', + 'my_table', + 'postgresql_user', + 'password') + ; + ``` + + View the [`mysql` table function](/sql-reference/table-functions/mysql) + docs page for more details. +
+
+ + + ClickHouse can read data from any ODBC or JDBC data source: + + ```sql + SELECT * + FROM + odbc( + 'DSN=mysqlconn', + 'my_database', + 'my_table' + ); + ``` + + View the [`odbc` table function](/sql-reference/table-functions/odbc) + and the [`jdbc` table function](/sql-reference/table-functions/jdbc) docs + pages for more details. +
+
+ + + Message queues can stream data into ClickHouse using the corresponding table + engine, including: + + - **Kafka**: integrate with Kafka using the [`Kafka` table engine](/engines/table-engines/integrations/kafka) + - **Amazon MSK**: integrate with [Amazon Managed Streaming for Apache Kafka (MSK)](/integrations/kafka/cloud/amazon-msk/) + - **RabbitMQ**: integrate with RabbitMQ using the [`RabbitMQ` table engine](/engines/table-engines/integrations/rabbitmq) +
+
+ + + ClickHouse has table functions to read data from the following sources: + + - **Hadoop**: integrate with Apache Hadoop using the [`hdfs` table function](/sql-reference/table-functions/hdfs) + - **Hudi**: read from existing Apache Hudi tables in S3 using the [`hudi` table function](/sql-reference/table-functions/hudi) + - **Iceberg**: read from existing Apache Iceberg tables in S3 using the [`iceberg` table function](/sql-reference/table-functions/iceberg) + - **DeltaLake**: read from existing Delta Lake tables in S3 using the [`deltaLake` table function](/sql-reference/table-functions/deltalake) +
+
+ + + Check out our [long list of ClickHouse integrations](/integrations) to find how to connect your existing frameworks and data sources to ClickHouse. +
+
+
+ +## Next steps + +- Check out our [Core Concepts](/managing-data/core-concepts) section to learn some of the fundamentals of how ClickHouse works under the hood. +- Check out the [Advanced Tutorial](tutorial.md) which takes a much deeper dive into the key concepts and capabilities of ClickHouse. +- Continue your learning by taking our free on-demand training courses at the [ClickHouse Academy](https://learn.clickhouse.com/visitor_class_catalog). +- We have a list of [example datasets](/getting-started/example-datasets/) with instructions on how to insert them. +- If your data is coming from an external source, view our [collection of integration guides](/integrations/) for connecting to message queues, databases, pipelines and more. +- If you are using a UI/BI visualization tool, view the [user guides for connecting a UI to ClickHouse](/integrations/data-visualization/). +- The user guide on [primary keys](/guides/best-practices/sparse-primary-indexes.md) is everything you need to know about primary keys and how to define them. + +
diff --git a/docusaurus.config.en.js b/docusaurus.config.en.js index 37d526ec4bf..54426a5c704 100644 --- a/docusaurus.config.en.js +++ b/docusaurus.config.en.js @@ -3,10 +3,13 @@ import math from "remark-math"; import katex from "rehype-katex"; import chHeader from "./plugins/header.js"; import fixLinks from "./src/hooks/fixLinks.js"; +const path = require('path'); +const remarkCustomBlocks = require('./plugins/remark-custom-blocks'); + +// Import custom plugins const { customParseFrontMatter } = require('./plugins/frontmatter-validation/customParseFrontMatter'); const checkFloatingPages = require('./plugins/checkFloatingPages'); const frontmatterValidator = require('./plugins/frontmatter-validation/frontmatterValidatorPlugin'); -const path = require('path'); import pluginLlmsTxt from './plugins/llms-txt-plugin.ts' // Helper function to skip over index.md files. @@ -156,7 +159,7 @@ const config = { showLastUpdateTime: false, sidebarCollapsed: true, routeBasePath: "/", - remarkPlugins: [math], + remarkPlugins: [math, remarkCustomBlocks], beforeDefaultRemarkPlugins: [fixLinks], rehypePlugins: [katex], }, @@ -360,7 +363,10 @@ const config = { pluginLlmsTxt, {} ], - ['./plugins/tailwind-config.js', {}], + [ + './plugins/tailwind-config.js', + {} + ] ], customFields: { blogSidebarLink: "/docs/knowledgebase", // Used for KB article page diff --git a/package.json b/package.json index 2d4198e9a86..fb76d3ceb97 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,8 @@ "remark-math": "^6.0.0", "sass": "^1.86.1", "search-insights": "^2.17.3", - "short-uuid": "^5.2.0" + "short-uuid": "^5.2.0", + "unist-util-visit": "^5.0.0" }, "devDependencies": { "@argos-ci/cli": "^2.5.5", diff --git a/plugins/remark-custom-blocks.js b/plugins/remark-custom-blocks.js new file mode 100644 index 00000000000..30b2c7c6f74 --- /dev/null +++ b/plugins/remark-custom-blocks.js @@ -0,0 +1,194 @@ +const { visit } = require('unist-util-visit'); + +// Log when the plugin file is loaded by Node.js +console.log('[remark-custom-blocks] Loading plugin from:', __filename); + +// --- Helper Functions --- +const extractText = (nodes) => { + let text = ''; + if (!nodes) return text; + for (const node of nodes) { + if (node.type === 'text') { + text += node.value; + } else if (node.children && Array.isArray(node.children)) { + text += extractText(node.children); + } + } + return text.trim(); +}; + +// Specific handler for the element +function handleVStepperElement(node, file) { + const filePath = file?.path || 'unknown file'; + console.log(`\n[remark-custom-blocks] --- Processing in ${filePath} ---`); + + // --- 1. Parse Attributes and Determine Mode --- + const jsxAttributes = node.attributes || []; + const parentProps = {}; // Props to pass to the final + let config = {}; + let mode = 'default'; // 'default' (firstActiveOnly), 'allExpanded', 'custom' + + const typeAttr = jsxAttributes.find(attr => attr.type === 'mdxJsxAttribute' && attr.name === 'type'); + if (typeAttr && typeof typeAttr.value === 'string') { + parentProps.type = typeAttr.value; + } + + const configAttr = jsxAttributes.find(attr => attr.type === 'mdxJsxAttribute' && attr.name === 'config'); + if (configAttr && typeof configAttr.value === 'string') { + try { + config = JSON.parse(configAttr.value); + mode = 'custom'; + console.log('[remark-custom-blocks] Using CUSTOM mode.'); + } catch (e) { + console.error(`[remark-custom-blocks] Invalid JSON in config attribute for ${filePath}: ${configAttr.value}`, e); + mode = 'default'; + } + } else { + const allExpandedAttr = jsxAttributes.find(attr => attr.type === 'mdxJsxAttribute' && attr.name === 'allExpanded'); + if (allExpandedAttr) { + mode = 'allExpanded'; + console.log('[remark-custom-blocks] Using allExpanded mode.'); + } else { + console.log('[remark-custom-blocks] Using default mode.'); + } + } + + // --- 2. Process Children (Content inside ) to Build Steps Data --- + const stepsData = []; + let currentStepContent = []; + let currentStepLabel = null; + let currentStepId = null; + let firstStepId = null; + const allStepIds = []; + let headingDepth = 2; // Assuming H2 defines steps + + const finalizeStep = () => { + if (currentStepLabel) { + stepsData.push({ + id: currentStepId, + label: currentStepLabel, + content: [...currentStepContent], + }); + } + currentStepContent = []; + }; + + if (node.children && node.children.length > 0) { + node.children.forEach((child) => { + if (child.type === 'heading' && child.depth === headingDepth) { + finalizeStep(); + currentStepLabel = extractText(child.children); + currentStepId = `step-${stepsData.length + 1}`; + allStepIds.push(currentStepId); + if (!firstStepId) { + firstStepId = currentStepId; + } + } else if (currentStepLabel) { + currentStepContent.push(child); + } + }); + } + finalizeStep(); // Finalize the last step + console.log(`[remark-custom-blocks] Found ${stepsData.length} steps.`); + + + // --- 3. Transform Parent Node ( to ) --- + node.name = 'VerticalStepper'; // Rename the element to the actual component + node.children = []; // Clear original children + node.attributes = []; // Reset attributes + + // Add parent props (like 'type') + Object.entries(parentProps).forEach(([key, value]) => { + node.attributes.push({ type: 'mdxJsxAttribute', name: key, value: value }); + }); + + // --- 4. Determine Active/Shown State based on Mode --- + let activeStepId = null; + let showStepIds = []; + let completedStepIds = []; + + if (mode === 'custom') { + activeStepId = config.initialActiveId || null; + showStepIds = config.initialShowIds || []; + completedStepIds = config.initialCompletedIds || []; + } else if (mode === 'allExpanded') { + activeStepId = firstStepId || null; + showStepIds = allStepIds; + } else { // Default mode + activeStepId = firstStepId || null; + showStepIds = firstStepId ? [firstStepId] : []; + } + + // --- 5. Generate Child Nodes with State Props --- + stepsData.forEach(step => { + const isActive = step.id === activeStepId; + const isShown = showStepIds.includes(step.id); + const isCompleted = completedStepIds.includes(step.id); + let status = 'incomplete'; + if (isActive) status = 'active'; + else if (isCompleted) status = 'complete'; + + // --- This is the boolean JS value --- + const collapsed = !isShown; + // ---------------------------------- + + // Build attributes for this Step component + const stepAttributes = [ + { type: 'mdxJsxAttribute', name: 'id', value: step.id }, + { type: 'mdxJsxAttribute', name: 'label', value: step.label }, + { type: 'mdxJsxAttribute', name: 'status', value: status }, + ]; + + // --- USE BOOLEAN ATTRIBUTE CONVENTION --- + // Add the 'collapsed' attribute ONLY if its value is true + if (collapsed === true) { + stepAttributes.push({ + type: 'mdxJsxAttribute', + name: 'collapsed', + value: null // Represents collapsed={true} + }); + // console.log(`[remark-custom-blocks] Step ${step.id}: Adding 'collapsed' attribute (as true)`); + } else { + // console.log(`[remark-custom-blocks] Step ${step.id}: Omitting 'collapsed' attribute (as false)`); + } + // --- END BOOLEAN ATTRIBUTE HANDLING --- + + // Push the Step node to the parent's children + node.children.push({ + type: 'mdxJsxFlowElement', + name: 'Step', // Use 'Step' - mapping happens in MDXComponents.js + attributes: stepAttributes, + children: step.content, // Content AST nodes + }); + }); + + console.log(`[remark-custom-blocks] Transformed in ${filePath} into with ${node.children.length} steps.`); + +} // <--- End of handleVStepperElement function + + +// --- Main Plugin Function --- +const plugin = (options) => { + const transformer = (tree, file) => { + // Target JSX elements + visit(tree, 'mdxJsxFlowElement', (node, index, parent) => { + // Route handling based on the element name used in Markdown + switch (node.name) { + case 'VStepper': + try { + handleVStepperElement(node, file); + } catch (error) { + const filePath = file?.path || 'unknown file'; + console.error(`[remark-custom-blocks] Error processing in ${filePath}:`, error); + } + break; + // Add more cases here later for other custom elements + default: + break; + } + }); + }; + return transformer; +}; + +module.exports = plugin; diff --git a/src/components/VerticalStepper/VerticalStepper.jsx b/src/components/VerticalStepper/VerticalStepper.jsx new file mode 100644 index 00000000000..b43d94afcb5 --- /dev/null +++ b/src/components/VerticalStepper/VerticalStepper.jsx @@ -0,0 +1,88 @@ +import React from 'react'; +// Import the ORIGINAL library component +import { VerticalStepper as OriginalVerticalStepper } from '@clickhouse/click-ui/bundled'; + +// Define props expected by the wrapper Step (from plugin) +interface StepWrapperProps { + children?: React.ReactNode; + // Props generated by the plugin: + id?: string; + label?: React.ReactNode; + status?: 'active' | 'complete' | 'incomplete'; + collapsed?: boolean | null; // Plugin passes null for true, undefined for false + // Props injected by parent wrapper: + index?: number; + isLast?: boolean; + stepperType?: 'numbered' | 'bulleted'; + // Allow pass-through of other props if needed + [key: string]: any; +} + +// Wrapper for the Step sub-component +const StepWrapper = ({ + children, + collapsed, // Receives null for true, undefined for false from plugin AST + ...restProps // id, label, status, index, isLast, stepperType etc. + }: StepWrapperProps) => { + + // Convert the incoming prop to the boolean expected by OriginalVerticalStepper.Step + // If prop is null (meaning true), pass true. Otherwise (prop is undefined), pass false. + const booleanCollapsed = (collapsed === null); + + console.log(`[Step Wrapper] Label: "${restProps.label}", Received collapsed: ${collapsed}, Passing boolean: ${booleanCollapsed}`); + + // Render the ORIGINAL library Step component + return ( + + {children} + + ); +}; + + +// Wrapper for the main VerticalStepper component +interface StepperWrapperProps { + children?: React.ReactNode; + type?: 'numbered' | 'bulleted'; + className?: string; + // Allow pass-through of other props if needed + [key: string]: any; +} + +const VerticalStepperWrapper = ({ + children, + type = 'numbered', // Get type from MDX tag attribute + ...props // Pass other props like className through + }: StepperWrapperProps) => { + + // Filter and clone children to inject index/isLast/type IF the original component needs them + // Check the OriginalVerticalStepper documentation/usage. Often, context is used. + // For simplicity, let's assume we might need to inject index/type if context isn't used by original. + const processedChildren = React.Children.toArray(children) + .filter(child => React.isValidElement(child) /* && child.type === StepWrapper - this check happens via MDX mapping */) + .map((step, index, arr) => { + return React.cloneElement(step as React.ReactElement, { + // Inject props needed by StepWrapper OR potentially OriginalVerticalStepper.Step itself + // It's cleaner if StepWrapper handles everything needed by Original. + // Check if OriginalVerticalStepper.Step uses index/isLast/stepperType props directly. + // Let's assume StepWrapper receives them and passes needed ones via ...restProps. + key: (step as React.ReactElement).props.id || index, // Add key prop + // index: index + 1, // If OriginalStep needs it + // isLast: index === arr.length - 1, // If OriginalStep needs it + // stepperType: type, // If OriginalStep needs it + }); + }); + + + // Render the ORIGINAL library VerticalStepper component + return ( + + {processedChildren} + + ); +}; + +// Attach the Step Wrapper for mapping purposes +VerticalStepperWrapper.Step = StepWrapper; + +export default VerticalStepperWrapper; \ No newline at end of file diff --git a/src/css/default.scss b/src/css/default.scss index b2342d46493..f97073a9f4e 100644 --- a/src/css/default.scss +++ b/src/css/default.scss @@ -1,7 +1,7 @@ /* You can override the default Infima variables here. */ :root { - --default-font: 14px; - --default-line-height: 21px; + --default-font: 16px; + --default-line-height: 28px; /* colors for right side table of contents */ --ifm-toc-link-color: var(--click-color-text-muted); diff --git a/src/theme/MDXComponents.js b/src/theme/MDXComponents.js new file mode 100644 index 00000000000..c0541e8dd8f --- /dev/null +++ b/src/theme/MDXComponents.js @@ -0,0 +1,10 @@ +import MDXComponents from '@theme-original/MDXComponents'; +import VerticalStepper from '@site/src/components/VerticalStepper/VerticalStepper'; + +export default { + // Re-use the default mapping + ...MDXComponents, + // Add custom components to the mapping + VerticalStepper: VerticalStepper, + Step: VerticalStepper.Step, +}; \ No newline at end of file From 58d281fcf278d657c45d1ec8c00e93d811ff59c6 Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Fri, 25 Apr 2025 15:02:45 +0200 Subject: [PATCH 3/8] Working - display all --- plugins/remark-custom-blocks.js | 277 +++++++----------- src/components/Stepper/Stepper.tsx | 131 +++++++++ .../VerticalStepper/VerticalStepper.jsx | 88 ------ src/theme/MDXComponents.js | 24 +- 4 files changed, 260 insertions(+), 260 deletions(-) create mode 100644 src/components/Stepper/Stepper.tsx delete mode 100644 src/components/VerticalStepper/VerticalStepper.jsx diff --git a/plugins/remark-custom-blocks.js b/plugins/remark-custom-blocks.js index 30b2c7c6f74..8e72ed4da54 100644 --- a/plugins/remark-custom-blocks.js +++ b/plugins/remark-custom-blocks.js @@ -1,8 +1,6 @@ +// plugins/remark-custom-blocks.js const { visit } = require('unist-util-visit'); -// Log when the plugin file is loaded by Node.js -console.log('[remark-custom-blocks] Loading plugin from:', __filename); - // --- Helper Functions --- const extractText = (nodes) => { let text = ''; @@ -17,178 +15,123 @@ const extractText = (nodes) => { return text.trim(); }; -// Specific handler for the element -function handleVStepperElement(node, file) { - const filePath = file?.path || 'unknown file'; - console.log(`\n[remark-custom-blocks] --- Processing in ${filePath} ---`); - - // --- 1. Parse Attributes and Determine Mode --- - const jsxAttributes = node.attributes || []; - const parentProps = {}; // Props to pass to the final - let config = {}; - let mode = 'default'; // 'default' (firstActiveOnly), 'allExpanded', 'custom' - - const typeAttr = jsxAttributes.find(attr => attr.type === 'mdxJsxAttribute' && attr.name === 'type'); - if (typeAttr && typeof typeAttr.value === 'string') { - parentProps.type = typeAttr.value; - } - - const configAttr = jsxAttributes.find(attr => attr.type === 'mdxJsxAttribute' && attr.name === 'config'); - if (configAttr && typeof configAttr.value === 'string') { - try { - config = JSON.parse(configAttr.value); - mode = 'custom'; - console.log('[remark-custom-blocks] Using CUSTOM mode.'); - } catch (e) { - console.error(`[remark-custom-blocks] Invalid JSON in config attribute for ${filePath}: ${configAttr.value}`, e); - mode = 'default'; - } - } else { - const allExpandedAttr = jsxAttributes.find(attr => attr.type === 'mdxJsxAttribute' && attr.name === 'allExpanded'); - if (allExpandedAttr) { - mode = 'allExpanded'; - console.log('[remark-custom-blocks] Using allExpanded mode.'); - } else { - console.log('[remark-custom-blocks] Using default mode.'); - } - } - - // --- 2. Process Children (Content inside ) to Build Steps Data --- - const stepsData = []; - let currentStepContent = []; - let currentStepLabel = null; - let currentStepId = null; - let firstStepId = null; - const allStepIds = []; - let headingDepth = 2; // Assuming H2 defines steps - - const finalizeStep = () => { - if (currentStepLabel) { - stepsData.push({ - id: currentStepId, - label: currentStepLabel, - content: [...currentStepContent], - }); - } - currentStepContent = []; - }; - - if (node.children && node.children.length > 0) { - node.children.forEach((child) => { - if (child.type === 'heading' && child.depth === headingDepth) { - finalizeStep(); - currentStepLabel = extractText(child.children); - currentStepId = `step-${stepsData.length + 1}`; - allStepIds.push(currentStepId); - if (!firstStepId) { - firstStepId = currentStepId; - } - } else if (currentStepLabel) { - currentStepContent.push(child); - } - }); - } - finalizeStep(); // Finalize the last step - console.log(`[remark-custom-blocks] Found ${stepsData.length} steps.`); - - - // --- 3. Transform Parent Node ( to ) --- - node.name = 'VerticalStepper'; // Rename the element to the actual component - node.children = []; // Clear original children - node.attributes = []; // Reset attributes - - // Add parent props (like 'type') - Object.entries(parentProps).forEach(([key, value]) => { - node.attributes.push({ type: 'mdxJsxAttribute', name: key, value: value }); - }); - - // --- 4. Determine Active/Shown State based on Mode --- - let activeStepId = null; - let showStepIds = []; - let completedStepIds = []; - - if (mode === 'custom') { - activeStepId = config.initialActiveId || null; - showStepIds = config.initialShowIds || []; - completedStepIds = config.initialCompletedIds || []; - } else if (mode === 'allExpanded') { - activeStepId = firstStepId || null; - showStepIds = allStepIds; - } else { // Default mode - activeStepId = firstStepId || null; - showStepIds = firstStepId ? [firstStepId] : []; - } - - // --- 5. Generate Child Nodes with State Props --- - stepsData.forEach(step => { - const isActive = step.id === activeStepId; - const isShown = showStepIds.includes(step.id); - const isCompleted = completedStepIds.includes(step.id); - let status = 'incomplete'; - if (isActive) status = 'active'; - else if (isCompleted) status = 'complete'; - - // --- This is the boolean JS value --- - const collapsed = !isShown; - // ---------------------------------- - - // Build attributes for this Step component - const stepAttributes = [ - { type: 'mdxJsxAttribute', name: 'id', value: step.id }, - { type: 'mdxJsxAttribute', name: 'label', value: step.label }, - { type: 'mdxJsxAttribute', name: 'status', value: status }, - ]; - - // --- USE BOOLEAN ATTRIBUTE CONVENTION --- - // Add the 'collapsed' attribute ONLY if its value is true - if (collapsed === true) { - stepAttributes.push({ - type: 'mdxJsxAttribute', - name: 'collapsed', - value: null // Represents collapsed={true} - }); - // console.log(`[remark-custom-blocks] Step ${step.id}: Adding 'collapsed' attribute (as true)`); - } else { - // console.log(`[remark-custom-blocks] Step ${step.id}: Omitting 'collapsed' attribute (as false)`); - } - // --- END BOOLEAN ATTRIBUTE HANDLING --- - - // Push the Step node to the parent's children - node.children.push({ - type: 'mdxJsxFlowElement', - name: 'Step', // Use 'Step' - mapping happens in MDXComponents.js - attributes: stepAttributes, - children: step.content, // Content AST nodes - }); - }); - - console.log(`[remark-custom-blocks] Transformed in ${filePath} into with ${node.children.length} steps.`); - -} // <--- End of handleVStepperElement function - - // --- Main Plugin Function --- const plugin = (options) => { const transformer = (tree, file) => { - // Target JSX elements + // Target JSX elements in the AST visit(tree, 'mdxJsxFlowElement', (node, index, parent) => { - // Route handling based on the element name used in Markdown - switch (node.name) { - case 'VStepper': - try { - handleVStepperElement(node, file); - } catch (error) { - const filePath = file?.path || 'unknown file'; - console.error(`[remark-custom-blocks] Error processing in ${filePath}:`, error); + // Look specifically for the tag used in Markdown source + if (node.name === 'VStepper') { + try { + console.log('Processing VStepper tag'); + + // --- 1. Parse Attributes --- + const jsxAttributes = node.attributes || []; + let type = "numbered"; // Default type + let isExpanded = false; // Default not expanded + + // Extract attributes + jsxAttributes.forEach(attr => { + if (attr.type === 'mdxJsxAttribute') { + if (attr.name === 'type' && typeof attr.value === 'string') { + type = attr.value; + console.log(`Found type: ${type}`); + } + else if (attr.name === 'allExpanded') { + isExpanded = true; + console.log('Found allExpanded attribute'); + } + } + }); + + // --- 2. Process Children to Build Steps Data --- + const stepsData = []; + let currentStepContent = []; + let currentStepLabel = null; + let currentStepId = null; + + const finalizeStep = () => { + if (currentStepLabel) { + stepsData.push({ + id: currentStepId, + label: currentStepLabel, + content: [...currentStepContent], // Collect content AST nodes + }); + console.log(`Finalized step: ${currentStepLabel}`); + } + currentStepContent = []; + }; + + if (node.children && node.children.length > 0) { + node.children.forEach((child) => { + if (child.type === 'heading' && child.depth === 2) { + finalizeStep(); // Finalize the previous step first + currentStepLabel = extractText(child.children); + currentStepId = `step-${stepsData.length + 1}`; + console.log(`Found heading: ${currentStepLabel}`); + } else if (currentStepLabel) { + // Only collect content nodes *after* a heading has defined a step + currentStepContent.push(child); + } + }); + } + finalizeStep(); // Finalize the last step found + + console.log(`Found ${stepsData.length} steps`); + + // --- 3. Transform Parent Node ( to ) --- + node.name = 'VerticalStepper'; // Use the name expected by MDXComponents mapping + node.children = []; // Clear original children from tag + + // Set attributes - using a simple string for expanded mode + node.attributes = [ + { type: 'mdxJsxAttribute', name: 'type', value: type }, + ]; + + // Add expanded attribute as a string (safer than boolean in MDX) + if (isExpanded) { + node.attributes.push({ + type: 'mdxJsxAttribute', + name: 'expanded', + value: 'true' + }); + console.log('Added expanded="true" attribute'); } - break; - // Add more cases here later for other custom elements - default: - break; + + // --- 4. Generate Child Nodes --- + stepsData.forEach(step => { + // Basic attributes for Step + const stepAttributes = [ + { type: 'mdxJsxAttribute', name: 'id', value: step.id }, + { type: 'mdxJsxAttribute', name: 'label', value: step.label }, + ]; + + // If expanded mode, add a "forceExpanded" attribute to each step + if (isExpanded) { + stepAttributes.push({ + type: 'mdxJsxAttribute', + name: 'forceExpanded', + value: 'true' + }); + } + + // Push the Step node + node.children.push({ + type: 'mdxJsxFlowElement', + name: 'Step', + attributes: stepAttributes, + children: step.content, + }); + console.log(`Added step: ${step.label}`); + }); + } catch (error) { + const filePath = file?.path || 'unknown file'; + console.error(`Error processing in ${filePath}:`, error); + } } }); }; return transformer; }; -module.exports = plugin; +module.exports = plugin; \ No newline at end of file diff --git a/src/components/Stepper/Stepper.tsx b/src/components/Stepper/Stepper.tsx new file mode 100644 index 00000000000..0e8b4b103cb --- /dev/null +++ b/src/components/Stepper/Stepper.tsx @@ -0,0 +1,131 @@ +// src/components/Stepper/Stepper.tsx +import React, { useState, useEffect, useRef, useCallback } from 'react'; +// Import the ORIGINAL library component +import { VerticalStepper as OriginalVerticalStepper } from '@clickhouse/click-ui/bundled'; + +// --- Step Component --- +interface StepProps { + children?: React.ReactNode; + id?: string; + label?: React.ReactNode; + forceExpanded?: string; // String attribute from remark plugin + isActive?: boolean; // Passed from parent + [key: string]: any; +} + +const Step = ({ + children, + id, + label, + forceExpanded, + isActive = false, + ...restProps + }: StepProps) => { + // Determine if this step should be expanded + // Either it's forced expanded or it's the active step + const isExpanded = forceExpanded === 'true' || isActive; + + // Convert to proper props for original component + const status: 'active' | 'incomplete' = isExpanded ? 'active' : 'incomplete'; + const collapsed = !isExpanded; + + console.log(`Step ${id}: forceExpanded=${forceExpanded}, isActive=${isActive}, collapsed=${collapsed}`); + + // Filter out props that shouldn't go to DOM + const { forceExpanded: _, isActive: __, ...domSafeProps } = restProps; + + return ( + + {children} + + ); +}; + +// --- Main VerticalStepper Component --- +interface StepperProps { + children?: React.ReactNode; + type?: 'numbered' | 'bulleted'; + className?: string; + expanded?: string; // String attribute from remark plugin + [key: string]: any; +} + +const VerticalStepper = ({ + children, + type = 'numbered', + className, + expanded, // String from remark plugin + ...props + }: StepperProps) => { + // Component-wide expanded mode + const isExpandedMode = expanded === 'true'; + console.log(`VerticalStepper: expanded=${expanded}, isExpandedMode=${isExpandedMode}`); + + // For non-expanded mode, we need active step tracking + const [activeStepId, setActiveStepId] = useState(null); + + // Get array of child steps + const childSteps = React.Children.toArray(children) + .filter(child => React.isValidElement(child) && child.type === Step); + + // If not in expanded mode, set the first step as active initially + useEffect(() => { + if (!isExpandedMode && childSteps.length > 0 && !activeStepId) { + const firstStep = childSteps[0] as React.ReactElement; + const firstStepId = firstStep.props.id || 'step-1'; + console.log(`Setting initial active step: ${firstStepId}`); + setActiveStepId(firstStepId); + } + }, [childSteps, isExpandedMode, activeStepId]); + + // Prepare children with keys and active state + const enhancedChildren = childSteps.map((child, index) => { + const childElement = child as React.ReactElement; + const stepId = childElement.props.id || `step-${index + 1}`; + const isActive = stepId === activeStepId; + + return React.cloneElement(childElement, { + key: stepId, + id: stepId, + isActive, + }); + }); + + // Handle step click + const handleStepClick = (stepId: string) => { + if (!isExpandedMode) { + console.log(`Activating step: ${stepId}`); + setActiveStepId(stepId); + } + }; + + // Filter out custom props + const { expanded: _, ...domProps } = props; + + return ( +
{ + // Find closest step element and activate it + const stepEl = (e.target as HTMLElement).closest('[data-step-id]'); + if (stepEl) { + const stepId = stepEl.getAttribute('data-step-id'); + if (stepId) handleStepClick(stepId); + } + }}> + + {enhancedChildren} + +
+ ); +}; + +// Attach the Step for mapping purposes +VerticalStepper.Step = Step; + +export default VerticalStepper; \ No newline at end of file diff --git a/src/components/VerticalStepper/VerticalStepper.jsx b/src/components/VerticalStepper/VerticalStepper.jsx deleted file mode 100644 index b43d94afcb5..00000000000 --- a/src/components/VerticalStepper/VerticalStepper.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; -// Import the ORIGINAL library component -import { VerticalStepper as OriginalVerticalStepper } from '@clickhouse/click-ui/bundled'; - -// Define props expected by the wrapper Step (from plugin) -interface StepWrapperProps { - children?: React.ReactNode; - // Props generated by the plugin: - id?: string; - label?: React.ReactNode; - status?: 'active' | 'complete' | 'incomplete'; - collapsed?: boolean | null; // Plugin passes null for true, undefined for false - // Props injected by parent wrapper: - index?: number; - isLast?: boolean; - stepperType?: 'numbered' | 'bulleted'; - // Allow pass-through of other props if needed - [key: string]: any; -} - -// Wrapper for the Step sub-component -const StepWrapper = ({ - children, - collapsed, // Receives null for true, undefined for false from plugin AST - ...restProps // id, label, status, index, isLast, stepperType etc. - }: StepWrapperProps) => { - - // Convert the incoming prop to the boolean expected by OriginalVerticalStepper.Step - // If prop is null (meaning true), pass true. Otherwise (prop is undefined), pass false. - const booleanCollapsed = (collapsed === null); - - console.log(`[Step Wrapper] Label: "${restProps.label}", Received collapsed: ${collapsed}, Passing boolean: ${booleanCollapsed}`); - - // Render the ORIGINAL library Step component - return ( - - {children} - - ); -}; - - -// Wrapper for the main VerticalStepper component -interface StepperWrapperProps { - children?: React.ReactNode; - type?: 'numbered' | 'bulleted'; - className?: string; - // Allow pass-through of other props if needed - [key: string]: any; -} - -const VerticalStepperWrapper = ({ - children, - type = 'numbered', // Get type from MDX tag attribute - ...props // Pass other props like className through - }: StepperWrapperProps) => { - - // Filter and clone children to inject index/isLast/type IF the original component needs them - // Check the OriginalVerticalStepper documentation/usage. Often, context is used. - // For simplicity, let's assume we might need to inject index/type if context isn't used by original. - const processedChildren = React.Children.toArray(children) - .filter(child => React.isValidElement(child) /* && child.type === StepWrapper - this check happens via MDX mapping */) - .map((step, index, arr) => { - return React.cloneElement(step as React.ReactElement, { - // Inject props needed by StepWrapper OR potentially OriginalVerticalStepper.Step itself - // It's cleaner if StepWrapper handles everything needed by Original. - // Check if OriginalVerticalStepper.Step uses index/isLast/stepperType props directly. - // Let's assume StepWrapper receives them and passes needed ones via ...restProps. - key: (step as React.ReactElement).props.id || index, // Add key prop - // index: index + 1, // If OriginalStep needs it - // isLast: index === arr.length - 1, // If OriginalStep needs it - // stepperType: type, // If OriginalStep needs it - }); - }); - - - // Render the ORIGINAL library VerticalStepper component - return ( - - {processedChildren} - - ); -}; - -// Attach the Step Wrapper for mapping purposes -VerticalStepperWrapper.Step = StepWrapper; - -export default VerticalStepperWrapper; \ No newline at end of file diff --git a/src/theme/MDXComponents.js b/src/theme/MDXComponents.js index c0541e8dd8f..3103542dbe5 100644 --- a/src/theme/MDXComponents.js +++ b/src/theme/MDXComponents.js @@ -1,10 +1,24 @@ +// src/theme/MDXComponents.js +import React from 'react'; import MDXComponents from '@theme-original/MDXComponents'; -import VerticalStepper from '@site/src/components/VerticalStepper/VerticalStepper'; -export default { - // Re-use the default mapping +// Import the custom Stepper component +// Make sure the path matches your project structure +import VerticalStepper from '@site/src/components/Stepper/Stepper'; + +// Define the enhanced components +const enhancedComponents = { ...MDXComponents, - // Add custom components to the mapping + + // Map to the components expected from the remark plugin VerticalStepper: VerticalStepper, Step: VerticalStepper.Step, -}; \ No newline at end of file +}; + +// Debug info +console.log('MDXComponents mapping loaded', { + hasVerticalStepper: !!VerticalStepper, + hasStep: !!(VerticalStepper && VerticalStepper.Step) +}); + +export default enhancedComponents; \ No newline at end of file From c785aec002057cc085bcef5f102f3b44c1fc21b7 Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Sat, 26 Apr 2025 17:31:37 +0200 Subject: [PATCH 4/8] display on scroll working --- ...d-quick-start.md => cloud-quick-start.mdx} | 14 +- docs/{getting-started => }/quick-start.mdx | 14 +- plugins/remark-custom-blocks.js | 65 +++--- sidebars.js | 2 +- src/components/Stepper/Stepper.tsx | 185 ++++++++++++------ src/theme/MDXComponents.js | 12 +- 6 files changed, 178 insertions(+), 114 deletions(-) rename docs/cloud/get-started/{cloud-quick-start.md => cloud-quick-start.mdx} (98%) rename docs/{getting-started => }/quick-start.mdx (98%) diff --git a/docs/cloud/get-started/cloud-quick-start.md b/docs/cloud/get-started/cloud-quick-start.mdx similarity index 98% rename from docs/cloud/get-started/cloud-quick-start.md rename to docs/cloud/get-started/cloud-quick-start.mdx index c984024ea81..9dcb1416485 100644 --- a/docs/cloud/get-started/cloud-quick-start.md +++ b/docs/cloud/get-started/cloud-quick-start.mdx @@ -27,7 +27,9 @@ import SQLConsoleDetail from '@site/docs/_snippets/_launch_sql_console.md'; The quickest and easiest way to get up and running with ClickHouse is to create a new service in [ClickHouse Cloud](https://console.clickhouse.cloud). -## 1. Create a ClickHouse service {#1-create-a-clickhouse-service} + + +## Create a ClickHouse service {#1-create-a-clickhouse-service} To create a free ClickHouse service in [ClickHouse Cloud](https://console.clickhouse.cloud), you just need to sign up by completing the following steps: @@ -67,22 +69,20 @@ Users can customize the service resources if required, specifying a minimum and Congratulations! Your ClickHouse Cloud service is up and running and onboarding is complete. Keep reading for details on how to start ingesting and querying your data. -## 2. Connect to ClickHouse {#2-connect-to-clickhouse} +## Connect to ClickHouse {#2-connect-to-clickhouse} There are 2 ways to connect to ClickHouse: - Connect using our web-based SQL console - Connect with your app - +
### Connect using SQL console {#connect-using-sql-console} For getting started quickly, ClickHouse provides a web-based SQL console to which you will be redirected on completing onboarding. SQL Console -
Create a query tab and enter a simple query to verify that your connection is working: -
```sql SHOW databases ``` @@ -104,7 +104,7 @@ Press the connect button from the navigation menu. A modal will open offering th If you can't see your language client, you may want to check our list of [Integrations](/integrations). -## 3. Add data {#3-add-data} +## Add data {#3-add-data} ClickHouse is better with data! There are multiple ways to add data and most of them are available on the Data Sources page, which can be accessed in the navigation menu. @@ -322,6 +322,8 @@ Suppose we have the following text in a CSV file named `data.csv`: New rows from CSV file
+
+ ## What's Next? {#whats-next} - The [Tutorial](/tutorial.md) has you insert 2 million rows into a table and write some analytical queries diff --git a/docs/getting-started/quick-start.mdx b/docs/quick-start.mdx similarity index 98% rename from docs/getting-started/quick-start.mdx rename to docs/quick-start.mdx index 249b117bb9a..652451b0f80 100644 --- a/docs/getting-started/quick-start.mdx +++ b/docs/quick-start.mdx @@ -13,19 +13,21 @@ import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; import {VerticalStepper} from '@clickhouse/click-ui/bundled'; -Welcome to ClickHouse! +_Welcome to ClickHouse!_ In this quick-start tutorial we'll get you set-up in 8 easy steps. You'll download an appropriate binary for your OS, learn to run ClickHouse server, and use the ClickHouse client to create a table, insert data into it and run a query to select that data. -All you'll need to follow along is your laptop, and curl or another command-line -HTTP client to fetch the ClickHouse binary. +All you'll need to follow along is: +- your laptop +- curl or another command-line +HTTP client to fetch the ClickHouse binary -Let's get started, shall we? +_Let's get started, shall we?_ - + ## Download the binary @@ -378,4 +380,4 @@ technologies that integrate with ClickHouse. - If you are using a UI/BI visualization tool, view the [user guides for connecting a UI to ClickHouse](/integrations/data-visualization/). - The user guide on [primary keys](/guides/best-practices/sparse-primary-indexes.md) is everything you need to know about primary keys and how to define them. - +
diff --git a/plugins/remark-custom-blocks.js b/plugins/remark-custom-blocks.js index 8e72ed4da54..e34b9ae92d4 100644 --- a/plugins/remark-custom-blocks.js +++ b/plugins/remark-custom-blocks.js @@ -1,4 +1,6 @@ // plugins/remark-custom-blocks.js +// VERSION BEFORE anchorId/slug logic was added + const { visit } = require('unist-util-visit'); // --- Helper Functions --- @@ -18,28 +20,29 @@ const extractText = (nodes) => { // --- Main Plugin Function --- const plugin = (options) => { const transformer = (tree, file) => { + // Target JSX elements in the AST visit(tree, 'mdxJsxFlowElement', (node, index, parent) => { - // Look specifically for the tag used in Markdown source - if (node.name === 'VStepper') { + // Look specifically for the tag used in Markdown source (as originally written) + if (node.name === 'VerticalStepper') { // <-- Checks for VerticalStepper from original code try { - console.log('Processing VStepper tag'); + console.log('Processing VStepper tag'); // Log from original code // --- 1. Parse Attributes --- const jsxAttributes = node.attributes || []; let type = "numbered"; // Default type - let isExpanded = false; // Default not expanded + let isExpanded = false; // Default not expanded (allExpanded) // Extract attributes jsxAttributes.forEach(attr => { if (attr.type === 'mdxJsxAttribute') { if (attr.name === 'type' && typeof attr.value === 'string') { type = attr.value; - console.log(`Found type: ${type}`); + console.log(`Found type: ${type}`); // Log from original code } - else if (attr.name === 'allExpanded') { + else if (attr.name === 'allExpanded') { // Check for allExpanded isExpanded = true; - console.log('Found allExpanded attribute'); + console.log('Found allExpanded attribute'); // Log from original code } } }); @@ -49,17 +52,20 @@ const plugin = (options) => { let currentStepContent = []; let currentStepLabel = null; let currentStepId = null; + // No anchorId variable here const finalizeStep = () => { if (currentStepLabel) { stepsData.push({ - id: currentStepId, - label: currentStepLabel, - content: [...currentStepContent], // Collect content AST nodes + id: currentStepId, // step-X ID + label: currentStepLabel, // Plain text label + // No anchorId here + content: [...currentStepContent], }); - console.log(`Finalized step: ${currentStepLabel}`); + console.log(`Finalized step: ${currentStepLabel}`); // Log from original code } currentStepContent = []; + currentStepLabel = null; // Reset label }; if (node.children && node.children.length > 0) { @@ -67,8 +73,9 @@ const plugin = (options) => { if (child.type === 'heading' && child.depth === 2) { finalizeStep(); // Finalize the previous step first currentStepLabel = extractText(child.children); - currentStepId = `step-${stepsData.length + 1}`; - console.log(`Found heading: ${currentStepLabel}`); + currentStepId = `step-${stepsData.length + 1}`; // Generate step-X ID + // No anchor extraction here + console.log(`Found heading: ${currentStepLabel}`); // Log from original code } else if (currentStepLabel) { // Only collect content nodes *after* a heading has defined a step currentStepContent.push(child); @@ -77,36 +84,37 @@ const plugin = (options) => { } finalizeStep(); // Finalize the last step found - console.log(`Found ${stepsData.length} steps`); + console.log(`Found ${stepsData.length} steps`); // Log from original code - // --- 3. Transform Parent Node ( to ) --- - node.name = 'VerticalStepper'; // Use the name expected by MDXComponents mapping - node.children = []; // Clear original children from tag + // --- 3. Transform Parent Node --- + // Transforms to VerticalStepper to match MDXComponents.js + node.name = 'Stepper'; + node.children = []; // Clear original children - // Set attributes - using a simple string for expanded mode + // Set attributes - type and expanded (if isExpanded is true) node.attributes = [ { type: 'mdxJsxAttribute', name: 'type', value: type }, ]; - - // Add expanded attribute as a string (safer than boolean in MDX) if (isExpanded) { node.attributes.push({ type: 'mdxJsxAttribute', - name: 'expanded', + name: 'expanded', // Pass 'expanded' prop to React component value: 'true' }); - console.log('Added expanded="true" attribute'); + console.log('Added expanded="true" attribute'); // Log from original code } // --- 4. Generate Child Nodes --- stepsData.forEach(step => { // Basic attributes for Step const stepAttributes = [ - { type: 'mdxJsxAttribute', name: 'id', value: step.id }, - { type: 'mdxJsxAttribute', name: 'label', value: step.label }, + { type: 'mdxJsxAttribute', name: 'id', value: step.id }, // step-X + { type: 'mdxJsxAttribute', name: 'label', value: step.label }, // Plain text + // No anchorId attribute ]; - // If expanded mode, add a "forceExpanded" attribute to each step + // Add forceExpanded attribute if parent was expanded + // (Matches React prop name used before anchor logic) if (isExpanded) { stepAttributes.push({ type: 'mdxJsxAttribute', @@ -118,14 +126,15 @@ const plugin = (options) => { // Push the Step node node.children.push({ type: 'mdxJsxFlowElement', - name: 'Step', + name: 'Step', // Output Step tag attributes: stepAttributes, - children: step.content, + children: step.content, // Pass content nodes as children }); - console.log(`Added step: ${step.label}`); + console.log(`Added step: ${step.label}`); // Log from original code }); } catch (error) { const filePath = file?.path || 'unknown file'; + // Added error logging console.error(`Error processing in ${filePath}:`, error); } } diff --git a/sidebars.js b/sidebars.js index 48ba7dd8749..232758d2a0e 100644 --- a/sidebars.js +++ b/sidebars.js @@ -14,7 +14,7 @@ const sidebars = { link: { type: "doc", id: "introduction-index" }, items: [ "intro", - "getting-started/quick-start", + "quick-start", "tutorial", "getting-started/install", "deployment-modes", diff --git a/src/components/Stepper/Stepper.tsx b/src/components/Stepper/Stepper.tsx index 0e8b4b103cb..c5120e78fa2 100644 --- a/src/components/Stepper/Stepper.tsx +++ b/src/components/Stepper/Stepper.tsx @@ -1,15 +1,14 @@ -// src/components/Stepper/Stepper.tsx -import React, { useState, useEffect, useRef, useCallback } from 'react'; -// Import the ORIGINAL library component +import React, { useState, useEffect } from 'react'; import { VerticalStepper as OriginalVerticalStepper } from '@clickhouse/click-ui/bundled'; // --- Step Component --- interface StepProps { children?: React.ReactNode; - id?: string; + id?: string; // step-X ID label?: React.ReactNode; - forceExpanded?: string; // String attribute from remark plugin - isActive?: boolean; // Passed from parent + forceExpanded?: string; // From parent 'expanded' state + isFirstStep?: boolean; // Prop calculated by parent + isActiveStep?: boolean; // Prop calculated by parent (based on state/scroll) [key: string]: any; } @@ -18,28 +17,36 @@ const Step = ({ id, label, forceExpanded, - isActive = false, + isFirstStep = false, + isActiveStep = false, ...restProps }: StepProps) => { - // Determine if this step should be expanded - // Either it's forced expanded or it's the active step - const isExpanded = forceExpanded === 'true' || isActive; - // Convert to proper props for original component - const status: 'active' | 'incomplete' = isExpanded ? 'active' : 'incomplete'; - const collapsed = !isExpanded; + // Logic from before anchor fixes: + // Determine 'active' status based on props passed from parent + const shouldBeActive = isFirstStep || isActiveStep || forceExpanded === 'true'; + const status: 'active' | 'complete' | 'incomplete' = shouldBeActive ? 'active' : 'incomplete'; - console.log(`Step ${id}: forceExpanded=${forceExpanded}, isActive=${isActive}, collapsed=${collapsed}`); + // Let underlying component handle expansion based on status='active' + // We pass collapsed=true, relying on status='active' to override it. + const collapsed = true; - // Filter out props that shouldn't go to DOM - const { forceExpanded: _, isActive: __, ...domSafeProps } = restProps; + // console.log(`Step ${id}: isFirstStep=${isFirstStep}, isActiveStep=${isActiveStep}, status=${status}, collapsed=${collapsed}`); + + // Filter out props specific to this wrapper logic + const { + forceExpanded: _, + isFirstStep: __, + isActiveStep: ___, + ...domSafeProps // Pass the rest to the underlying component + } = restProps; return ( {children} @@ -52,80 +59,130 @@ interface StepperProps { children?: React.ReactNode; type?: 'numbered' | 'bulleted'; className?: string; - expanded?: string; // String attribute from remark plugin + expanded?: string; // Corresponds to allExpanded in MDX [key: string]: any; } -const VerticalStepper = ({ +// Using VerticalStepper name based on MDXComponents.js +const VStepper = ({ children, type = 'numbered', className, - expanded, // String from remark plugin + expanded, // 'true' if allExpanded was set ...props }: StepperProps) => { - // Component-wide expanded mode - const isExpandedMode = expanded === 'true'; - console.log(`VerticalStepper: expanded=${expanded}, isExpandedMode=${isExpandedMode}`); - // For non-expanded mode, we need active step tracking - const [activeStepId, setActiveStepId] = useState(null); + // State for tracking active steps via scroll/click + const [activeStepIds, setActiveStepIds] = useState>(new Set()); + + // Determine if all steps should be expanded from the start + const isExpandedMode = expanded === 'true'; - // Get array of child steps + // Get children and filter out non-elements const childSteps = React.Children.toArray(children) - .filter(child => React.isValidElement(child) && child.type === Step); + .filter(child => React.isValidElement(child)); + + // Extract step-X IDs (used for state tracking and keys) + const stepIds = childSteps.map((child, index) => { + const childElement = child as React.ReactElement; + return childElement.props.id || `step-${index + 1}`; + }); - // If not in expanded mode, set the first step as active initially + // --- Scroll Listener Effect (with CORRECT selectors) --- useEffect(() => { - if (!isExpandedMode && childSteps.length > 0 && !activeStepId) { - const firstStep = childSteps[0] as React.ReactElement; - const firstStepId = firstStep.props.id || 'step-1'; - console.log(`Setting initial active step: ${firstStepId}`); - setActiveStepId(firstStepId); - } - }, [childSteps, isExpandedMode, activeStepId]); - - // Prepare children with keys and active state + if (isExpandedMode) return; + + const handleScroll = () => { + // --- Uses the CORRECT selectors --- + const headers = document.querySelectorAll('button[id^="step-"]'); + if (headers.length === 0) { + console.log('No step headers found using CORRECT selectors'); + return; + } + // console.log(`Found ${headers.length} step headers using CORRECT selectors`); + + headers.forEach((header, index) => { + if (index >= stepIds.length) return; + const rect = header.getBoundingClientRect(); + const isVisible = rect.top < window.innerHeight * 0.7 && rect.bottom > 0; + if (isVisible) { + const stepId = stepIds[index]; + setActiveStepIds(prev => { + if (prev.has(stepId)) return prev; + // console.log(`Activating step ${stepId} from scroll`); + return new Set([...prev, stepId]); + }); + } + }); + }; + + const timeoutId = setTimeout(handleScroll, 500); + window.addEventListener('scroll', handleScroll); + const intervals = [1000, 2000, 3000].map(delay => setTimeout(handleScroll, delay)); + + return () => { + window.removeEventListener('scroll', handleScroll); + clearTimeout(timeoutId); + intervals.forEach(id => clearTimeout(id)); + }; + }, [isExpandedMode, stepIds]); + + // Click handler (used within onClick prop below) + const handleStepClick = (stepId: string) => { + if (isExpandedMode) return; + // console.log(`Clicked on step ${stepId}`); + setActiveStepIds(prev => new Set([...prev, stepId])); + }; + + // Prepare children, passing down calculated state const enhancedChildren = childSteps.map((child, index) => { const childElement = child as React.ReactElement; const stepId = childElement.props.id || `step-${index + 1}`; - const isActive = stepId === activeStepId; + const isActiveStep = activeStepIds.has(stepId); // Is this step activated by scroll/click? + const isFirstStep = index === 0; // Is this the first step? return React.cloneElement(childElement, { key: stepId, id: stepId, - isActive, + isFirstStep, // Pass down flag for first step logic + isActiveStep, // Pass down flag for active state logic + forceExpanded: isExpandedMode ? 'true' : undefined // Pass down expanded mode }); }); - // Handle step click - const handleStepClick = (stepId: string) => { - if (!isExpandedMode) { - console.log(`Activating step: ${stepId}`); - setActiveStepId(stepId); - } - }; - - // Filter out custom props + // Filter out custom props before passing to underlying component const { expanded: _, ...domProps } = props; return ( -
{ - // Find closest step element and activate it - const stepEl = (e.target as HTMLElement).closest('[data-step-id]'); - if (stepEl) { - const stepId = stepEl.getAttribute('data-step-id'); - if (stepId) handleStepClick(stepId); - } - }}> - - {enhancedChildren} - -
+ { + if (isExpandedMode) return; + const target = e.target as HTMLElement; + // --- Uses the CORRECT selector --- + const header = target.closest('button[id^="step-"]'); + if (header) { + // --- Uses the CORRECT selector --- + const allHeaders = document.querySelectorAll('button[id^="step-"]'); + const index = Array.from(allHeaders).indexOf(header as Element); + if (index !== -1 && index < stepIds.length) { + const stepId = stepIds[index]; + handleStepClick(stepId); // Call handler to update state + // Removed stopPropagation unless needed + } + } + }} + > + {enhancedChildren} + ); }; -// Attach the Step for mapping purposes -VerticalStepper.Step = Step; +// Attach the Step component +VStepper.Step = Step; -export default VerticalStepper; \ No newline at end of file +// Export the main component +export default VStepper; \ No newline at end of file diff --git a/src/theme/MDXComponents.js b/src/theme/MDXComponents.js index 3103542dbe5..626feb75980 100644 --- a/src/theme/MDXComponents.js +++ b/src/theme/MDXComponents.js @@ -4,21 +4,15 @@ import MDXComponents from '@theme-original/MDXComponents'; // Import the custom Stepper component // Make sure the path matches your project structure -import VerticalStepper from '@site/src/components/Stepper/Stepper'; +import VStepper from '@site/src/components/Stepper/Stepper'; // Define the enhanced components const enhancedComponents = { ...MDXComponents, // Map to the components expected from the remark plugin - VerticalStepper: VerticalStepper, - Step: VerticalStepper.Step, + Stepper: VStepper, + Step: VStepper.Step, }; -// Debug info -console.log('MDXComponents mapping loaded', { - hasVerticalStepper: !!VerticalStepper, - hasStep: !!(VerticalStepper && VerticalStepper.Step) -}); - export default enhancedComponents; \ No newline at end of file From 4434b9ddeb17f6e5cb41a640c7c23c5f30f1c2f4 Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Mon, 28 Apr 2025 11:15:01 +0200 Subject: [PATCH 5/8] Anchors visible but link checker still failing --- plugins/remark-custom-blocks.js | 45 +++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/plugins/remark-custom-blocks.js b/plugins/remark-custom-blocks.js index e34b9ae92d4..e2a04ea68e2 100644 --- a/plugins/remark-custom-blocks.js +++ b/plugins/remark-custom-blocks.js @@ -52,17 +52,16 @@ const plugin = (options) => { let currentStepContent = []; let currentStepLabel = null; let currentStepId = null; - // No anchorId variable here + let currentAnchorId = null; const finalizeStep = () => { if (currentStepLabel) { stepsData.push({ id: currentStepId, // step-X ID label: currentStepLabel, // Plain text label - // No anchorId here + anchorId: currentAnchorId, content: [...currentStepContent], }); - console.log(`Finalized step: ${currentStepLabel}`); // Log from original code } currentStepContent = []; currentStepLabel = null; // Reset label @@ -70,12 +69,13 @@ const plugin = (options) => { if (node.children && node.children.length > 0) { node.children.forEach((child) => { + console.log(child) if (child.type === 'heading' && child.depth === 2) { finalizeStep(); // Finalize the previous step first currentStepLabel = extractText(child.children); + currentAnchorId = child.data.hProperties.id; currentStepId = `step-${stepsData.length + 1}`; // Generate step-X ID - // No anchor extraction here - console.log(`Found heading: ${currentStepLabel}`); // Log from original code + console.log(`Found heading: ${currentStepLabel} \{#${currentAnchorId}\}`); // Log from original code } else if (currentStepLabel) { // Only collect content nodes *after* a heading has defined a step currentStepContent.push(child); @@ -110,7 +110,6 @@ const plugin = (options) => { const stepAttributes = [ { type: 'mdxJsxAttribute', name: 'id', value: step.id }, // step-X { type: 'mdxJsxAttribute', name: 'label', value: step.label }, // Plain text - // No anchorId attribute ]; // Add forceExpanded attribute if parent was expanded @@ -123,14 +122,44 @@ const plugin = (options) => { }); } + const anchorElement = { + type: 'mdxJsxFlowElement', + name: 'a', + attributes: [ + { type: 'mdxJsxAttribute', name: 'href', value: `#${step.anchorId}` }, + { type: 'mdxJsxAttribute', name: 'className', value: 'hash-link' } + ], + children: [ + // Add a link symbol or text + { + type: 'text', + value: '🔗' // Or any other symbol/text you prefer + } + ] + }; + + const contentWithAnchor = [ + { + type: 'mdxJsxFlowElement', + name: 'div', + attributes: [ + { type: 'mdxJsxAttribute', name: 'id', value: step.anchorId } + ], + children: [ + anchorElement, // Add the anchor element + ...step.content // Then add the regular content + ] + } + ]; + // Push the Step node node.children.push({ type: 'mdxJsxFlowElement', name: 'Step', // Output Step tag attributes: stepAttributes, - children: step.content, // Pass content nodes as children + children: contentWithAnchor, // Pass content nodes as children }); - console.log(`Added step: ${step.label}`); // Log from original code + console.log(`Added step: ${step.label} with anchorId: ${step.anchorId || 'none'}`); // Log from original code }); } catch (error) { const filePath = file?.path || 'unknown file'; From e2edb7f3bd2163083edb45380bad7938bec0abed Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Sun, 4 May 2025 18:57:10 +0200 Subject: [PATCH 6/8] Final version - working --- docs/quick-start.mdx | 19 ++++----- plugins/remark-custom-blocks.js | 62 +++++------------------------- src/components/Stepper/Stepper.tsx | 33 ++++++++++------ 3 files changed, 37 insertions(+), 77 deletions(-) diff --git a/docs/quick-start.mdx b/docs/quick-start.mdx index 652451b0f80..d86e0b78fd7 100644 --- a/docs/quick-start.mdx +++ b/docs/quick-start.mdx @@ -13,23 +13,18 @@ import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; import {VerticalStepper} from '@clickhouse/click-ui/bundled'; -_Welcome to ClickHouse!_ +**Welcome to ClickHouse!** -In this quick-start tutorial we'll get you set-up in 8 +In this quick-start tutorial, we'll get you set up in 8 easy steps. You'll download an appropriate binary for your OS, learn to run ClickHouse server, and use the ClickHouse client to create a table, -insert data into it and run a query to select that data. +then insert data into it and run a query to select that data. -All you'll need to follow along is: -- your laptop -- curl or another command-line -HTTP client to fetch the ClickHouse binary +Let's get started? -_Let's get started, shall we?_ + - - -## Download the binary +## Download ClickHouse ClickHouse runs natively on Linux, FreeBSD and macOS, and runs on Windows via the [WSL](https://learn.microsoft.com/en-us/windows/wsl/about). The simplest way to download ClickHouse locally is to run the @@ -370,7 +365,7 @@ technologies that integrate with ClickHouse. -## Next steps +## Explore - Check out our [Core Concepts](/managing-data/core-concepts) section to learn some of the fundamentals of how ClickHouse works under the hood. - Check out the [Advanced Tutorial](tutorial.md) which takes a much deeper dive into the key concepts and capabilities of ClickHouse. diff --git a/plugins/remark-custom-blocks.js b/plugins/remark-custom-blocks.js index e2a04ea68e2..1080beccca2 100644 --- a/plugins/remark-custom-blocks.js +++ b/plugins/remark-custom-blocks.js @@ -1,6 +1,3 @@ -// plugins/remark-custom-blocks.js -// VERSION BEFORE anchorId/slug logic was added - const { visit } = require('unist-util-visit'); // --- Helper Functions --- @@ -23,26 +20,19 @@ const plugin = (options) => { // Target JSX elements in the AST visit(tree, 'mdxJsxFlowElement', (node, index, parent) => { - // Look specifically for the tag used in Markdown source (as originally written) - if (node.name === 'VerticalStepper') { // <-- Checks for VerticalStepper from original code + // Look specifically for the tag used in the markdown file + if (node.name === 'VerticalStepper') { try { - console.log('Processing VStepper tag'); // Log from original code - - // --- 1. Parse Attributes --- + // --- 1. Parse Attributes --- const jsxAttributes = node.attributes || []; let type = "numbered"; // Default type - let isExpanded = false; // Default not expanded (allExpanded) + let isExpanded = true; // Extract attributes jsxAttributes.forEach(attr => { if (attr.type === 'mdxJsxAttribute') { if (attr.name === 'type' && typeof attr.value === 'string') { type = attr.value; - console.log(`Found type: ${type}`); // Log from original code - } - else if (attr.name === 'allExpanded') { // Check for allExpanded - isExpanded = true; - console.log('Found allExpanded attribute'); // Log from original code } } }); @@ -69,13 +59,12 @@ const plugin = (options) => { if (node.children && node.children.length > 0) { node.children.forEach((child) => { - console.log(child) if (child.type === 'heading' && child.depth === 2) { finalizeStep(); // Finalize the previous step first currentStepLabel = extractText(child.children); currentAnchorId = child.data.hProperties.id; currentStepId = `step-${stepsData.length + 1}`; // Generate step-X ID - console.log(`Found heading: ${currentStepLabel} \{#${currentAnchorId}\}`); // Log from original code + currentStepContent.push(child); // We need the header otherwise onBrokenAnchors fails } else if (currentStepLabel) { // Only collect content nodes *after* a heading has defined a step currentStepContent.push(child); @@ -84,14 +73,12 @@ const plugin = (options) => { } finalizeStep(); // Finalize the last step found - console.log(`Found ${stepsData.length} steps`); // Log from original code - // --- 3. Transform Parent Node --- - // Transforms to VerticalStepper to match MDXComponents.js + // Transforms to to match src/theme/MDXComponents.js node.name = 'Stepper'; node.children = []; // Clear original children - // Set attributes - type and expanded (if isExpanded is true) + // Set attributes node.attributes = [ { type: 'mdxJsxAttribute', name: 'type', value: type }, ]; @@ -122,44 +109,13 @@ const plugin = (options) => { }); } - const anchorElement = { - type: 'mdxJsxFlowElement', - name: 'a', - attributes: [ - { type: 'mdxJsxAttribute', name: 'href', value: `#${step.anchorId}` }, - { type: 'mdxJsxAttribute', name: 'className', value: 'hash-link' } - ], - children: [ - // Add a link symbol or text - { - type: 'text', - value: '🔗' // Or any other symbol/text you prefer - } - ] - }; - - const contentWithAnchor = [ - { - type: 'mdxJsxFlowElement', - name: 'div', - attributes: [ - { type: 'mdxJsxAttribute', name: 'id', value: step.anchorId } - ], - children: [ - anchorElement, // Add the anchor element - ...step.content // Then add the regular content - ] - } - ]; - // Push the Step node node.children.push({ type: 'mdxJsxFlowElement', name: 'Step', // Output Step tag attributes: stepAttributes, - children: contentWithAnchor, // Pass content nodes as children + children: [...step.content], // Pass content nodes as children }); - console.log(`Added step: ${step.label} with anchorId: ${step.anchorId || 'none'}`); // Log from original code }); } catch (error) { const filePath = file?.path || 'unknown file'; @@ -172,4 +128,4 @@ const plugin = (options) => { return transformer; }; -module.exports = plugin; \ No newline at end of file +module.exports = plugin; diff --git a/src/components/Stepper/Stepper.tsx b/src/components/Stepper/Stepper.tsx index c5120e78fa2..c5f1fbc8302 100644 --- a/src/components/Stepper/Stepper.tsx +++ b/src/components/Stepper/Stepper.tsx @@ -22,7 +22,6 @@ const Step = ({ ...restProps }: StepProps) => { - // Logic from before anchor fixes: // Determine 'active' status based on props passed from parent const shouldBeActive = isFirstStep || isActiveStep || forceExpanded === 'true'; const status: 'active' | 'complete' | 'incomplete' = shouldBeActive ? 'active' : 'incomplete'; @@ -31,7 +30,22 @@ const Step = ({ // We pass collapsed=true, relying on status='active' to override it. const collapsed = true; - // console.log(`Step ${id}: isFirstStep=${isFirstStep}, isActiveStep=${isActiveStep}, status=${status}, collapsed=${collapsed}`); + // Swap out the Click-UI Stepper label (shown next to the numbered circle) for the + // H2 header so that Docusaurus doesn't fail on broken anchor checks + useEffect(() => { + try { + const button = document.querySelectorAll(`button[id^=${id}]`)[0]; + const divChildren = Array.from(button.children).filter(el => el.tagName === 'DIV'); + const label = divChildren[1]; + const content = button.nextElementSibling; + const header = content.querySelectorAll('h2')[0] + header.style.margin = '0'; + button.append(header) + label.remove() + } catch (e) { + console.log('Error occurred in Stepper.tsx while swapping H2 for Click-UI label') + } + }, []); // Filter out props specific to this wrapper logic const { @@ -46,7 +60,7 @@ const Step = ({ label={label} status={status} collapsed={collapsed} - id={id} // Pass step-X ID + id={id} {...domSafeProps} > {children} @@ -88,18 +102,17 @@ const VStepper = ({ return childElement.props.id || `step-${index + 1}`; }); - // --- Scroll Listener Effect (with CORRECT selectors) --- + // --- Scroll Listener Effect --- + // This is currently not used, but we may want to include it at a later stage useEffect(() => { if (isExpandedMode) return; const handleScroll = () => { - // --- Uses the CORRECT selectors --- const headers = document.querySelectorAll('button[id^="step-"]'); if (headers.length === 0) { console.log('No step headers found using CORRECT selectors'); return; } - // console.log(`Found ${headers.length} step headers using CORRECT selectors`); headers.forEach((header, index) => { if (index >= stepIds.length) return; @@ -158,20 +171,16 @@ const VStepper = ({ type={type} className={className} {...domProps} - // --- onClick Handler (with CORRECT selectors) --- onClick={(e) => { if (isExpandedMode) return; const target = e.target as HTMLElement; - // --- Uses the CORRECT selector --- const header = target.closest('button[id^="step-"]'); if (header) { - // --- Uses the CORRECT selector --- const allHeaders = document.querySelectorAll('button[id^="step-"]'); const index = Array.from(allHeaders).indexOf(header as Element); if (index !== -1 && index < stepIds.length) { const stepId = stepIds[index]; - handleStepClick(stepId); // Call handler to update state - // Removed stopPropagation unless needed + handleStepClick(stepId); } } }} @@ -185,4 +194,4 @@ const VStepper = ({ VStepper.Step = Step; // Export the main component -export default VStepper; \ No newline at end of file +export default VStepper; From de072560109f56e0fe2cfaed2a1f6522a2a7132e Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Sun, 4 May 2025 19:06:14 +0200 Subject: [PATCH 7/8] small fixes --- docs/quick-start.mdx | 2 +- plugins/remark-custom-blocks.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/quick-start.mdx b/docs/quick-start.mdx index d86e0b78fd7..ff9b2050afa 100644 --- a/docs/quick-start.mdx +++ b/docs/quick-start.mdx @@ -24,7 +24,7 @@ Let's get started? -## Download ClickHouse +## Download ClickHouse {#download-the-binary} ClickHouse runs natively on Linux, FreeBSD and macOS, and runs on Windows via the [WSL](https://learn.microsoft.com/en-us/windows/wsl/about). The simplest way to download ClickHouse locally is to run the diff --git a/plugins/remark-custom-blocks.js b/plugins/remark-custom-blocks.js index 1080beccca2..bec708c684f 100644 --- a/plugins/remark-custom-blocks.js +++ b/plugins/remark-custom-blocks.js @@ -88,7 +88,6 @@ const plugin = (options) => { name: 'expanded', // Pass 'expanded' prop to React component value: 'true' }); - console.log('Added expanded="true" attribute'); // Log from original code } // --- 4. Generate Child Nodes --- From 7574b9b6165f7c3f19d94eea37984d497274d63b Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Tue, 6 May 2025 15:07:26 +0200 Subject: [PATCH 8/8] code cleanup and add stepper to advanced tutorial --- docs/quick-start.mdx | 18 +++--- docs/tutorial.md | 4 ++ plugins/remark-custom-blocks.js | 2 +- src/components/Stepper/Stepper.tsx | 92 ++++-------------------------- 4 files changed, 26 insertions(+), 90 deletions(-) diff --git a/docs/quick-start.mdx b/docs/quick-start.mdx index ff9b2050afa..4cc7551a3d7 100644 --- a/docs/quick-start.mdx +++ b/docs/quick-start.mdx @@ -251,18 +251,18 @@ technologies that integrate with ClickHouse. Query id: 8ca9b2f9-65a2-4982-954a-890de710a336 - ┌─name──────┬─type────────────────────┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┬─ttl_expression─┐ - │ id │ Nullable(Int64) │ │ │ │ │ │ - │ type │ Nullable(String) │ │ │ │ │ │ - │ author │ Nullable(String) │ │ │ │ │ │ - │ timestamp │ Nullable(DateTime64(9)) │ │ │ │ │ │ - │ comment │ Nullable(String) │ │ │ │ │ │ - │ children │ Array(Nullable(Int64)) │ │ │ │ │ │ - └───────────┴─────────────────────────┴──────────────┴────────────────────┴─────────┴──────────────────┴────────────────┘ + ┌─name──────┬─type────────────────────┐ + │ id │ Nullable(Int64) │ + │ type │ Nullable(String) │ + │ author │ Nullable(String) │ + │ timestamp │ Nullable(DateTime64(9)) │ + │ comment │ Nullable(String) │ + │ children │ Array(Nullable(Int64)) │ + └───────────┴─────────────────────────┘ ``` Notice ClickHouse infers the names and data types of your columns by analyzing a - large batch of rows. If ClickHouse can not determine the storage type from the + large batch of rows. If ClickHouse can not determine the file format from the filename, you can specify it as the second argument: ```sql diff --git a/docs/tutorial.md b/docs/tutorial.md index fb60ef43e52..98641bc290c 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -17,6 +17,8 @@ Learn how to ingest and query data in ClickHouse using a New York City taxi exam You need access to a running ClickHouse service to complete this tutorial. For instructions, see the [Quick Start](./quick-start.mdx) guide. + + ## Create a new table {#create-a-new-table} The New York City taxi dataset contains details about millions of taxi rides, with columns including tip amount, tolls, payment type, and more. Create a table to store this data. @@ -508,6 +510,7 @@ Write some queries that join the `taxi_zone_dictionary` with your `trips` table. Generally, we avoid using `SELECT *` often in ClickHouse. You should only retrieve the columns you actually need. However, this query is slower for the purposes of the example. ::: + ## Next steps {#next-steps} @@ -517,3 +520,4 @@ Learn more about ClickHouse with the following documentation: - [Integrate an external data source](/integrations/index.mdx): Review data source integration options, including files, Kafka, PostgreSQL, data pipelines, and many others. - [Visualize data in ClickHouse](./integrations/data-visualization/index.md): Connect your favorite UI/BI tool to ClickHouse. - [SQL Reference](./sql-reference/index.md): Browse the SQL functions available in ClickHouse for transforming, processing and analyzing data. + diff --git a/plugins/remark-custom-blocks.js b/plugins/remark-custom-blocks.js index bec708c684f..cdaad6bc908 100644 --- a/plugins/remark-custom-blocks.js +++ b/plugins/remark-custom-blocks.js @@ -20,7 +20,7 @@ const plugin = (options) => { // Target JSX elements in the AST visit(tree, 'mdxJsxFlowElement', (node, index, parent) => { - // Look specifically for the tag used in the markdown file + // Look specifically for the tag used in the markdown file if (node.name === 'VerticalStepper') { try { // --- 1. Parse Attributes --- diff --git a/src/components/Stepper/Stepper.tsx b/src/components/Stepper/Stepper.tsx index c5f1fbc8302..245c78e86f6 100644 --- a/src/components/Stepper/Stepper.tsx +++ b/src/components/Stepper/Stepper.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { VerticalStepper as OriginalVerticalStepper } from '@clickhouse/click-ui/bundled'; // --- Step Component --- @@ -8,7 +8,6 @@ interface StepProps { label?: React.ReactNode; forceExpanded?: string; // From parent 'expanded' state isFirstStep?: boolean; // Prop calculated by parent - isActiveStep?: boolean; // Prop calculated by parent (based on state/scroll) [key: string]: any; } @@ -18,21 +17,18 @@ const Step = ({ label, forceExpanded, isFirstStep = false, - isActiveStep = false, ...restProps }: StepProps) => { // Determine 'active' status based on props passed from parent - const shouldBeActive = isFirstStep || isActiveStep || forceExpanded === 'true'; + const shouldBeActive = isFirstStep || forceExpanded === 'true'; const status: 'active' | 'complete' | 'incomplete' = shouldBeActive ? 'active' : 'incomplete'; // Let underlying component handle expansion based on status='active' - // We pass collapsed=true, relying on status='active' to override it. const collapsed = true; - // Swap out the Click-UI Stepper label (shown next to the numbered circle) for the - // H2 header so that Docusaurus doesn't fail on broken anchor checks - useEffect(() => { + // Swap out the Click-UI Stepper label for the H2 header + React.useEffect(() => { try { const button = document.querySelectorAll(`button[id^=${id}]`)[0]; const divChildren = Array.from(button.children).filter(el => el.tagName === 'DIV'); @@ -45,13 +41,12 @@ const Step = ({ } catch (e) { console.log('Error occurred in Stepper.tsx while swapping H2 for Click-UI label') } - }, []); + }, [id]); // Filter out props specific to this wrapper logic const { forceExpanded: _, isFirstStep: __, - isActiveStep: ___, ...domSafeProps // Pass the rest to the underlying component } = restProps; @@ -79,15 +74,12 @@ interface StepperProps { // Using VerticalStepper name based on MDXComponents.js const VStepper = ({ - children, - type = 'numbered', - className, - expanded, // 'true' if allExpanded was set - ...props - }: StepperProps) => { - - // State for tracking active steps via scroll/click - const [activeStepIds, setActiveStepIds] = useState>(new Set()); + children, + type = 'numbered', + className, + expanded, // 'true' if allExpanded was set + ...props + }: StepperProps) => { // Determine if all steps should be expanded from the start const isExpandedMode = expanded === 'true'; @@ -96,69 +88,22 @@ const VStepper = ({ const childSteps = React.Children.toArray(children) .filter(child => React.isValidElement(child)); - // Extract step-X IDs (used for state tracking and keys) + // Extract step-X IDs (used for keys) const stepIds = childSteps.map((child, index) => { const childElement = child as React.ReactElement; return childElement.props.id || `step-${index + 1}`; }); - // --- Scroll Listener Effect --- - // This is currently not used, but we may want to include it at a later stage - useEffect(() => { - if (isExpandedMode) return; - - const handleScroll = () => { - const headers = document.querySelectorAll('button[id^="step-"]'); - if (headers.length === 0) { - console.log('No step headers found using CORRECT selectors'); - return; - } - - headers.forEach((header, index) => { - if (index >= stepIds.length) return; - const rect = header.getBoundingClientRect(); - const isVisible = rect.top < window.innerHeight * 0.7 && rect.bottom > 0; - if (isVisible) { - const stepId = stepIds[index]; - setActiveStepIds(prev => { - if (prev.has(stepId)) return prev; - // console.log(`Activating step ${stepId} from scroll`); - return new Set([...prev, stepId]); - }); - } - }); - }; - - const timeoutId = setTimeout(handleScroll, 500); - window.addEventListener('scroll', handleScroll); - const intervals = [1000, 2000, 3000].map(delay => setTimeout(handleScroll, delay)); - - return () => { - window.removeEventListener('scroll', handleScroll); - clearTimeout(timeoutId); - intervals.forEach(id => clearTimeout(id)); - }; - }, [isExpandedMode, stepIds]); - - // Click handler (used within onClick prop below) - const handleStepClick = (stepId: string) => { - if (isExpandedMode) return; - // console.log(`Clicked on step ${stepId}`); - setActiveStepIds(prev => new Set([...prev, stepId])); - }; - // Prepare children, passing down calculated state const enhancedChildren = childSteps.map((child, index) => { const childElement = child as React.ReactElement; const stepId = childElement.props.id || `step-${index + 1}`; - const isActiveStep = activeStepIds.has(stepId); // Is this step activated by scroll/click? const isFirstStep = index === 0; // Is this the first step? return React.cloneElement(childElement, { key: stepId, id: stepId, isFirstStep, // Pass down flag for first step logic - isActiveStep, // Pass down flag for active state logic forceExpanded: isExpandedMode ? 'true' : undefined // Pass down expanded mode }); }); @@ -171,19 +116,6 @@ const VStepper = ({ type={type} className={className} {...domProps} - onClick={(e) => { - if (isExpandedMode) return; - const target = e.target as HTMLElement; - const header = target.closest('button[id^="step-"]'); - if (header) { - const allHeaders = document.querySelectorAll('button[id^="step-"]'); - const index = Array.from(allHeaders).indexOf(header as Element); - if (index !== -1 && index < stepIds.length) { - const stepId = stepIds[index]; - handleStepClick(stepId); - } - } - }} > {enhancedChildren}