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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions contribute/style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,48 @@ vale --filter='.Name == "ClickHouse.Headings"' docs/integrations
This will run only the rule named `Headings` on
the `docs/integrations` directory. Specifying a specific markdown
file is also possible.

## Vertical numbered stepper

It is possible to render numbered steppers, as seen [here](https://clickhouse.com/docs/getting-started/quick-start/cloud)
for example, using the following syntax:

`<VerticalStepper headerLevel="hN"></VerticalStepper>`

For example:

```markdown
<VerticalStepper headerLevel="h2">
## Header 1 {#explicit-anchor-1}

Some content...

## Header 2 {#explicit-anchor-2}

Some more content...

</VerticalStepper>
```

You should specify `N` as the header level you want the vertical stepper to render
for. In the example above, it is `h2` as we are using `##`. Use `h3` for `###`,
`h4` for `####` etc.

The component also works with numbered lists using `headerLevel="list"`. For example:

```markdown
<VerticalStepper headerLevel="h2">

1. First list item

Some content...

2. Second list item

Some more content...

</VerticalStepper>
```

In this case, the first paragraph will be taken to be the label (the text next
to the numbered circles of the vertical stepper) of the stepper.
22 changes: 17 additions & 5 deletions docs/integrations/data-ingestion/clickpipes/aws-privatelink.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ To set up PrivateLink with VPC resource:
2. Create a resource configuration
3. Create a resource share

#### 1. Create a resource gateway {#create-resource-gateway}
<VerticalStepper headerLevel="h4">

#### Create a resource gateway {#create-resource-gateway}

Resource gateway is the point that receives traffic for specified resources in your VPC.

Expand Down Expand Up @@ -85,7 +87,7 @@ aws vpc-lattice get-resource-gateway \
--resource-gateway-identifier <RESOURCE_GATEWAY_ID>
```

#### 2. Create a VPC Resource-Configuration {#create-resource-configuration}
#### Create a VPC Resource-Configuration {#create-resource-configuration}

Resource-Configuration is associated with resource gateway to make your resource accessible.

Expand Down Expand Up @@ -121,7 +123,7 @@ For more information, see the [AWS documentation](https://docs.aws.amazon.com/vp

The output will contain a Resource-Configuration ARN, which you will need for the next step. It will also contain a Resource-Configuration ID, which you will need to set up a ClickPipe connection with VPC resource.

#### 3. Create a Resource-Share {#create-resource-share}
#### Create a Resource-Share {#create-resource-share}

Sharing your resource requires a Resource-Share. This is facilitated through the Resource Access Manager (RAM).

Expand All @@ -143,6 +145,8 @@ You are ready to [create a ClickPipe with Reverse private endpoint](#creating-cl

For more details on PrivateLink with VPC resource, see [AWS documentation](https://docs.aws.amazon.com/vpc/latest/privatelink/privatelink-access-resources.html).

</VerticalStepper>

### MSK multi-VPC connectivity {#msk-multi-vpc}

The [Multi-VPC connectivity](https://docs.aws.amazon.com/msk/latest/developerguide/aws-access-mult-vpc.html) is a built-in feature of AWS MSK that allows you to connect multiple VPCs to a single MSK cluster.
Expand Down Expand Up @@ -188,6 +192,8 @@ can be configured for ClickPipes. Add [your ClickPipe region](#aws-privatelink-r

## Creating a ClickPipe with reverse private endpoint {#creating-clickpipe}

<VerticalStepper headerLevel="list">

1. Access the SQL Console for your ClickHouse Cloud Service.

<Image img={cp_service} alt="ClickPipes service" size="md" border/>
Expand Down Expand Up @@ -242,22 +248,28 @@ For same-region access, creating a VPC Resource is the recommended approach.

To see a full list of DNS names, access it in the cloud service settings.

</VerticalStepper>

## Managing existing reverse private endpoints {#managing-existing-endpoints}

You can manage existing reverse private endpoints in the ClickHouse Cloud service settings:

<VerticalStepper headerLevel="list">

1. On a sidebar find the `Settings` button and click on it.

<Image img={cp_rpe_settings0} alt="ClickHouse Cloud settings" size="lg" border/>
<Image img={cp_rpe_settings0} alt="ClickHouse Cloud settings" size="lg" border/>

2. Click on `Reverse private endpoints` in a `ClickPipe reverse private endpoints` section.

<Image img={cp_rpe_settings1} alt="ClickHouse Cloud settings" size="md" border/>
<Image img={cp_rpe_settings1} alt="ClickHouse Cloud settings" size="md" border/>

Reverse private endpoint extended information is shown in the flyout.

Endpoint can be removed from here. It will affect any ClickPipes using this endpoint.

</VerticalStepper>

## Supported AWS regions {#aws-privatelink-regions}

AWS PrivateLink support is limited to specific AWS regions for ClickPipes.
Expand Down
107 changes: 88 additions & 19 deletions plugins/remark-custom-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ const extractText = (nodes) => {
return text.trim();
};

const extractRawContent = (nodes) => {
if (!nodes || !Array.isArray(nodes)) return '';
return nodes.map(node => {
if (node.type === 'text') {
return node.value;
} else if (node.type === 'inlineCode') {
return `\`${node.value}\``;
} else if (node.children) {
return extractRawContent(node.children);
}
return '';
}).join('');
};

// --- Main Plugin Function ---
const plugin = (options) => {
const transformer = (tree, file) => {
Expand All @@ -38,13 +52,17 @@ const plugin = (options) => {
type = attr.value;
} else if (attr.name === 'headerLevel' && typeof attr.value === 'string') {
let set_level = attr.value
const regex = /h([2-5])/;
const match = set_level.match(regex);
// If there's a match, convert the captured group to a number
if (match) {
headerLevel = Number(match[1]);
if (set_level === 'list') {
headerLevel = 'list';
} else {
throw new Error("VerticalStepper supported only for h2-5");
const regex = /h([2-5])/;
const match = set_level.match(regex);
// If there's a match, convert the captured group to a number
if (match) {
headerLevel = Number(match[1]);
} else {
throw new Error("VerticalStepper supported only for h2-5 or 'list'");
}
}
}
}
Expand Down Expand Up @@ -72,18 +90,47 @@ const plugin = (options) => {
};

if (node.children && node.children.length > 0) {
node.children.forEach((child) => {
if (child.type === 'heading' && child.depth === headerLevel) {
finalizeStep(); // Finalize the previous step first
currentStepLabel = extractText(child.children);
currentAnchorId = child.data?.hProperties?.id || null;
currentStepId = `step-${total_steps}`; // Generate step-X ID
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);
}
});
if (headerLevel === 'list') {
// Handle ordered list mode
node.children.forEach((child) => {
if (child.type === 'list' && child.ordered === true) {
// Process each list item as a step
child.children.forEach((listItem) => {
if (listItem.type === 'listItem' && listItem.children && listItem.children.length > 0) {
finalizeStep(); // Finalize the previous step first
// Extract the first paragraph as the step label
const firstChild = listItem.children[0];
if (firstChild && firstChild.type === 'paragraph') {
currentStepLabel = firstChild.children;
currentStepId = `step-${total_steps}`;
currentAnchorId = null;
// Include all list item content except the first paragraph (which becomes the label)
currentStepContent.push(...listItem.children.slice(1));
}
}
});
} else {
// Include other content (like paragraphs, images, etc.) in the current step
if (currentStepLabel) {
currentStepContent.push(child);
}
}
});
} else {
// Handle heading mode (original logic)
node.children.forEach((child) => {
if (child.type === 'heading' && child.depth === headerLevel) {
finalizeStep(); // Finalize the previous step first
currentStepLabel = extractText(child.children);
currentAnchorId = child.data?.hProperties?.id || null;
currentStepId = `step-${total_steps}`; // Generate step-X ID
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);
}
});
}
}
finalizeStep(); // Finalize the last step found

Expand All @@ -110,9 +157,31 @@ const plugin = (options) => {
// Basic attributes for Step
const stepAttributes = [
{ type: 'mdxJsxAttribute', name: 'id', value: step.id }, // step-X
{ type: 'mdxJsxAttribute', name: 'label', value: step.label }, // Plain text
];

// Add the label - for list mode, we'll create a special label element
if (headerLevel === 'list' && Array.isArray(step.label)) {
// For list mode, create a paragraph element with the label content and add it to the step children
const labelParagraph = {
type: 'paragraph',
children: [...step.label]
};
step.content.unshift(labelParagraph);

// Use plain text for the label attribute
stepAttributes.push({
type: 'mdxJsxAttribute',
name: 'label',
value: extractRawContent(step.label)
});
} else {
stepAttributes.push({
type: 'mdxJsxAttribute',
name: 'label',
value: step.label
});
}

// Add forceExpanded attribute if parent was expanded
// (Matches React prop name used before anchor logic)
if (isExpanded) {
Expand Down
30 changes: 24 additions & 6 deletions src/components/Stepper/Stepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,32 @@ const Step = ({
// Let underlying component handle expansion based on status='active'
const collapsed = true;

// Swap out the Click-UI Stepper label for the H2 header
// Swap out the Click-UI Stepper label for custom content
React.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(headerType)[0]
header.style.margin = '0';
button.append(header)

if (headerType === 'list') {
// For list mode, find the first paragraph (which contains the formatted label)
const firstParagraph = content.querySelector('p');
if (firstParagraph) {
const labelElement = firstParagraph.cloneNode(true);
(labelElement as HTMLElement).style.margin = '0';
button.append(labelElement);
firstParagraph.remove(); // Remove from content to avoid duplication
}
} else {
// For heading mode, use the header element
const header = content.querySelectorAll(headerType)[0]
if (header) {
(header as HTMLElement).style.margin = '0';
button.append(header)
}
}

label.remove()
} catch (e) {
console.log(`Error occurred in Stepper.tsx while swapping ${headerType} for Click-UI label:`, e)
Expand Down Expand Up @@ -71,7 +87,7 @@ interface StepperProps {
type?: 'numbered' | 'bulleted';
className?: string;
expanded?: string; // Corresponds to allExpanded in MDX
headerLevel?: number;
headerLevel?: number | string;
[key: string]: any;
}

Expand All @@ -89,7 +105,9 @@ const VStepper = ({
const isExpandedMode = expanded === 'true';

let hType = 'h2';
if (headerLevel > 2) {
if (headerLevel === 'list') {
hType = 'list';
} else if (headerLevel > 2) {
hType = `h${headerLevel}`
}

Expand Down
Loading