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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components/Layout/MDXWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Tiles } from './mdx/tiles';
import { PageHeader } from './mdx/PageHeader';
import Admonition from './mdx/Admonition';
import { MethodSignature } from './mdx/MethodSignature';
import { Tabs, Tab } from './mdx/Tabs';

import { Frontmatter, PageContextType } from './Layout';
import { ActivePage } from './utils/nav';
Expand Down Expand Up @@ -339,6 +340,8 @@ const MDXWrapper: React.FC<MDXWrapperProps> = ({ children, pageContext, location
td: Table.Cell,
Tiles,
MethodSignature,
Tabs,
Tab,
}}
>
<PageHeader title={title} intro={intro} />
Expand Down
100 changes: 100 additions & 0 deletions src/components/Layout/mdx/Tabs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { Tabs, Tab } from './Tabs';

describe('Tabs', () => {
it('renders tab buttons with author-defined labels', () => {
render(
<Tabs>
<Tab value="a" label="Alpha">
Content A
</Tab>
<Tab value="b" label="Beta">
Content B
</Tab>
</Tabs>,
);

expect(screen.getByRole('tab', { name: 'Alpha' })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: 'Beta' })).toBeInTheDocument();
});

it('shows the first tab content by default', () => {
render(
<Tabs>
<Tab value="a" label="Alpha">
Content A
</Tab>
<Tab value="b" label="Beta">
Content B
</Tab>
</Tabs>,
);

expect(screen.getByText('Content A')).toBeInTheDocument();
expect(screen.queryByText('Content B')).not.toBeInTheDocument();
});

it('switches content when a tab is clicked', () => {
render(
<Tabs>
<Tab value="a" label="Alpha">
Content A
</Tab>
<Tab value="b" label="Beta">
Content B
</Tab>
</Tabs>,
);

fireEvent.click(screen.getByRole('tab', { name: 'Beta' }));

expect(screen.queryByText('Content A')).not.toBeInTheDocument();
expect(screen.getByText('Content B')).toBeInTheDocument();
});

it('sets aria-selected correctly', () => {
render(
<Tabs>
<Tab value="a" label="Alpha">
Content A
</Tab>
<Tab value="b" label="Beta">
Content B
</Tab>
</Tabs>,
);

expect(screen.getByRole('tab', { name: 'Alpha' })).toHaveAttribute('aria-selected', 'true');
expect(screen.getByRole('tab', { name: 'Beta' })).toHaveAttribute('aria-selected', 'false');

fireEvent.click(screen.getByRole('tab', { name: 'Beta' }));

expect(screen.getByRole('tab', { name: 'Alpha' })).toHaveAttribute('aria-selected', 'false');
expect(screen.getByRole('tab', { name: 'Beta' })).toHaveAttribute('aria-selected', 'true');
});

it('renders a tabpanel for the active tab', () => {
render(
<Tabs>
<Tab value="a" label="Alpha">
Content A
</Tab>
<Tab value="b" label="Beta">
Content B
</Tab>
</Tabs>,
);

expect(screen.getByRole('tabpanel')).toHaveTextContent('Content A');
});

it('renders nothing for Tab used outside of Tabs', () => {
const { container } = render(
<Tab value="a" label="Alpha">
Orphan
</Tab>,
);
expect(container).toBeEmptyDOMElement();
});
});
75 changes: 75 additions & 0 deletions src/components/Layout/mdx/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useState, createContext, useContext, isValidElement, ReactNode, useId } from 'react';
import cn from '@ably/ui/core/utils/cn';

type TabsContextType = {
activeTab: string;
tabsId: string;
};

const TabsContext = createContext<TabsContextType | undefined>(undefined);

interface TabProps {
value: string;
label: string;
children: ReactNode;
}

export const Tab: React.FC<TabProps> = ({ value, children }) => {
const context = useContext(TabsContext);
if (!context) {
return null;
}
return context.activeTab === value ? (
<div role="tabpanel" id={`${context.tabsId}-panel-${value}`} aria-labelledby={`${context.tabsId}-tab-${value}`}>
{children}
</div>
) : null;
};

interface TabsProps {
children: ReactNode;
}

export const Tabs: React.FC<TabsProps> = ({ children }) => {
const tabsId = useId();

const tabs: { value: string; label: string }[] = [];
React.Children.forEach(children, (child) => {
if (isValidElement<TabProps>(child) && child.props.value) {
tabs.push({ value: child.props.value, label: child.props.label ?? child.props.value });
}
});

const [activeTab, setActiveTab] = useState(tabs[0]?.value ?? '');

return (
<TabsContext.Provider value={{ activeTab, tabsId }}>
<div className="my-5 border border-neutral-300 dark:border-neutral-800 rounded-lg overflow-hidden">
<div
className="flex gap-1 border-b border-neutral-300 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-1100 px-2 pt-2"
role="tablist"
>
{tabs.map(({ value, label }) => (
<button
key={value}
id={`${tabsId}-tab-${value}`}
role="tab"
aria-selected={activeTab === value}
aria-controls={`${tabsId}-panel-${value}`}
onClick={() => setActiveTab(value)}
className={cn(
'px-4 py-2 text-sm font-medium rounded-t-md transition-colors cursor-pointer',
activeTab === value
? 'bg-white dark:bg-neutral-1300 text-neutral-1300 dark:text-neutral-000 border border-neutral-300 dark:border-neutral-800 border-b-white dark:border-b-neutral-1300 -mb-px'
: 'text-neutral-700 dark:text-neutral-500 hover:text-neutral-1000 dark:hover:text-neutral-300',
)}
>
{label}
</button>
))}
</div>
<div className="p-5">{children}</div>
</div>
</TabsContext.Provider>
);
};
44 changes: 43 additions & 1 deletion src/pages/docs/channels/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,10 @@ The rules related to enabling features are:
| Message conflation | If enabled, messages are aggregated over a set period of time and evaluated against a conflation key. All but the latest message for each conflation key value will be discarded, and the resulting message, or messages, will be delivered to subscribers as a single batch once the period of time elapses. [Message conflation](/docs/messages#conflation) reduces costs in high-throughput scenarios by removing redundant and outdated messages. |
| Message annotations, updates, deletes, and appends | If enabled, allows message "annotations":/docs/messages/annotations to be used, as well as updates, deletes, and appends to be published to messages. Note that these features are currently in public preview. When this feature is enabled, messages will be "persisted":/docs/storage-history/storage#all-message-persistence (necessary in order from them later be annotated or updated), and "continuous history":/docs/storage-history/history#continuous-history features will not work.

To set a rule in the Ably dashboard:
To set a rule:

<Tabs>
<Tab value="dashboard" label="Dashboard">

1. Sign in to your Ably account.
2. Select an app.
Expand All @@ -211,6 +214,45 @@ To set a rule in the Ably dashboard:
5. Select channel name or namespace to apply rules to.
6. Check required rules.

</Tab>
<Tab value="control-api" label="Control API">

Create a rule by sending a `POST` request to [`/apps/{app_id}/namespaces`](/docs/api/control-api):

<Code>
```shell
curl -X POST https://control.ably.net/v1/apps/{APP_ID}/namespaces \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"id": "my-namespace",
"persisted": true,
"persistLast": false,
"pushEnabled": false,
"tlsOnly": false,
"authenticated": false
}'
```
</Code>

</Tab>
<Tab value="cli" label="Ably CLI">

Use the [Ably CLI](/docs/platform/tools/cli) to create a rule:

<Code>
```shell
ably apps rules create \
--name "my-namespace" \
--persisted
```
</Code>

Run `ably apps rules create --help` for a full list of available options.

</Tab>
</Tabs>

## Channel history <a id="history"/>

Channel [history](/docs/storage-history/history) enables clients to retrieve messages that have been previously published on the channel. Messages can be retrieved from history for up to 72 hours in the past, depending on the [persistence](/docs/storage-history/storage) configured for the channel.
Expand Down
45 changes: 34 additions & 11 deletions src/pages/docs/livesync/mongodb/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,44 @@ When a change event is received over the Change Streams API it is published to a

You first need to create an integration rule in order to sync your MongoDB instance with Ably.

There are two ways to create a MongoDB database connector rule:

1. Using the [Ably Dashboard](https://ably.com/dashboard).
2. Using the [Control API](/docs/platform/account/control-api).
<Tabs>
<Tab value="dashboard" label="Dashboard">

To create a rule in your [dashboard](https://ably.com/dashboard):

1. Log in and select the application you wish to use.
2. Click the *Integrations* tab.
3. Click the *New Integration Rule* button.
4. Choose *MongoDB* from the list.
2. Click the **Integrations** tab.
3. Click the **New Integration Rule** button.
4. Choose **MongoDB** from the list.
5. Configure the [rule settings](#config).

</Tab>
<Tab value="control-api" label="Control API">

Create a MongoDB rule by sending a `POST` request to [`/apps/{app_id}/rules`](/docs/api/control-api):

<Code>
```shell
curl -X POST https://control.ably.net/v1/apps/{APP_ID}/rules \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"ruleType": "ingress/mongodb",
"target": {
"url": "mongodb://user:pass@myhost.com",
"database": "my-database",
"collection": "my-collection",
"pipeline": "[{\"$set\": {\"_ablyChannel\": \"myDocuments\"}}]",
"fullDocument": "off",
"fullDocumentBeforeChange": "off",
"primarySite": "us-east-1-A"
}
}'
```
</Code>

</Tab>
</Tabs>

### Rule configuration <a id="config"/>

Expand All @@ -51,8 +78,6 @@ Use the following fields to configure your MongoDB integration rule:
| Pipeline | A MongoDB pipeline to pass to the Change Stream API. This field allows you to control which types of change events are published, and which channel the change event should be published to. The [pipeline](#pipeline) *must set the `_ablyChannel` field* on the root of the change event. It must also be a valid JSON array of [pipeline operations](https://www.mongodb.com/docs/v8.0/changeStreams/#modify-change-stream-output). |
| Primary site | The primary site that the connector will run in. You should choose a site that is close to your database. |
| Provisioned capacity | The provisioned capacity of the connector. It is always set to 1. |


## Subscribe to change events <a id="subscribe"/>

Use the [Ably Pub/Sub SDKs](/docs/sdks) to subscribe to changes published by the MongoDB database connector.
Expand Down Expand Up @@ -132,8 +157,6 @@ You must provide a MongoDB pipeline when configuring the integration rule. The p
</Code>

The pipeline also lets you filter and modify the change events published, as well as edit their structure.


The following is an example of a pipeline that only matches certain operation types before sending the change events to the `myDocuments` channel:

<Code>
Expand Down
41 changes: 35 additions & 6 deletions src/pages/docs/livesync/postgres/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,44 @@ By writing change events to the outbox table within the same database transactio

### Creating the rule <a id="create"/>

There are two ways to create a Postgres integration rule:
1. Using the [Ably Dashboard](https://ably.com/dashboard).
2. Using the [Control API](/docs/platform/account/control-api).
<Tabs>
<Tab value="dashboard" label="Dashboard">

To create a rule in your [dashboard](https://ably.com/dashboard):

1. Login and select the application you wish to use.
2. Click the *Integrations* tab.
3. Click the *New Integration Rule* button.
4. Choose *Postgres* from the list.
2. Click the **Integrations** tab.
3. Click the **New Integration Rule** button.
4. Choose **Postgres** from the list.
5. Configure the [rule settings](#configure).

</Tab>
<Tab value="control-api" label="Control API">

Create a Postgres rule by sending a `POST` request to [`/apps/{app_id}/rules`](/docs/api/control-api):

<Code>
```shell
curl -X POST https://control.ably.net/v1/apps/{APP_ID}/rules \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"ruleType": "ingress-postgres-outbox",
"target": {
"url": "postgres://user:password@example.com:5432/your-database-name",
"outboxTableSchema": "public",
"outboxTableName": "outbox",
"nodesTableSchema": "public",
"nodesTableName": "nodes",
"sslMode": "prefer",
"primarySite": "us-east-1-A"
}
}'
```
</Code>

</Tab>
</Tabs>

### Rule configuration <a id="configure"/>

Expand Down
Loading