diff --git a/src/components/Layout/MDXWrapper.tsx b/src/components/Layout/MDXWrapper.tsx index 8a1fddce6c..3b86d4bb82 100644 --- a/src/components/Layout/MDXWrapper.tsx +++ b/src/components/Layout/MDXWrapper.tsx @@ -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'; @@ -339,6 +340,8 @@ const MDXWrapper: React.FC = ({ children, pageContext, location td: Table.Cell, Tiles, MethodSignature, + Tabs, + Tab, }} > diff --git a/src/components/Layout/mdx/Tabs.test.tsx b/src/components/Layout/mdx/Tabs.test.tsx new file mode 100644 index 0000000000..003710ae83 --- /dev/null +++ b/src/components/Layout/mdx/Tabs.test.tsx @@ -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( + + + Content A + + + Content B + + , + ); + + expect(screen.getByRole('tab', { name: 'Alpha' })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Beta' })).toBeInTheDocument(); + }); + + it('shows the first tab content by default', () => { + render( + + + Content A + + + Content B + + , + ); + + expect(screen.getByText('Content A')).toBeInTheDocument(); + expect(screen.queryByText('Content B')).not.toBeInTheDocument(); + }); + + it('switches content when a tab is clicked', () => { + render( + + + Content A + + + Content B + + , + ); + + 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( + + + Content A + + + Content B + + , + ); + + 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( + + + Content A + + + Content B + + , + ); + + expect(screen.getByRole('tabpanel')).toHaveTextContent('Content A'); + }); + + it('renders nothing for Tab used outside of Tabs', () => { + const { container } = render( + + Orphan + , + ); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/src/components/Layout/mdx/Tabs.tsx b/src/components/Layout/mdx/Tabs.tsx new file mode 100644 index 0000000000..9d3cace28d --- /dev/null +++ b/src/components/Layout/mdx/Tabs.tsx @@ -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(undefined); + +interface TabProps { + value: string; + label: string; + children: ReactNode; +} + +export const Tab: React.FC = ({ value, children }) => { + const context = useContext(TabsContext); + if (!context) { + return null; + } + return context.activeTab === value ? ( +
+ {children} +
+ ) : null; +}; + +interface TabsProps { + children: ReactNode; +} + +export const Tabs: React.FC = ({ children }) => { + const tabsId = useId(); + + const tabs: { value: string; label: string }[] = []; + React.Children.forEach(children, (child) => { + if (isValidElement(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 ( + +
+
+ {tabs.map(({ value, label }) => ( + + ))} +
+
{children}
+
+
+ ); +}; diff --git a/src/pages/docs/channels/index.mdx b/src/pages/docs/channels/index.mdx index 1cf50357d0..41b1c41bba 100644 --- a/src/pages/docs/channels/index.mdx +++ b/src/pages/docs/channels/index.mdx @@ -202,14 +202,56 @@ 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: - -1. Sign in to your Ably account. -2. Select an app. -3. Go to **Settings** tab. -4. Click **Add new rule**. -5. Select channel name or namespace to apply rules to. -6. Check required rules. +To set a rule: + + + + +In your app [settings](https://ably.com/accounts/any/apps/any/settings): + +1. Click **Add new rule**. +2. Select the channel name or namespace to apply rules to. +3. Check the required rules. +4. Click **Create rule** to save. + + + + +Create a rule using the Control API by sending a `POST` request to [`/apps/{app_id}/namespaces`](/docs/api/control-api): + + +```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 + }' +``` + + + + + +Use the [Ably CLI](/docs/platform/tools/cli) to create a rule: + + +```shell +ably apps rules create \ + --name "my-namespace" \ + --persisted +``` + + +Run `ably apps rules create --help` for a full list of available options. + + + ## Channel history diff --git a/src/pages/docs/livesync/mongodb/index.mdx b/src/pages/docs/livesync/mongodb/index.mdx index 3adc1b20b1..76ad089c34 100644 --- a/src/pages/docs/livesync/mongodb/index.mdx +++ b/src/pages/docs/livesync/mongodb/index.mdx @@ -24,17 +24,42 @@ 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). +On the [Integrations](https://ably.com/accounts/any/apps/any/integrations) page of your app: -To create a rule in your [dashboard](https://ably.com/dashboard): +1. Click **New Integration Rule**. +2. Choose **MongoDB**. +3. Configure the [rule settings](#config). -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. + + + +Create a MongoDB rule using the Control API by sending a `POST` request to [`/apps/{app_id}/rules`](/docs/api/control-api): + + +```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" + } + }' +``` + + + + ### Rule configuration @@ -51,8 +76,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 Use the [Ably Pub/Sub SDKs](/docs/sdks) to subscribe to changes published by the MongoDB database connector. @@ -132,8 +155,6 @@ You must provide a MongoDB pipeline when configuring the integration rule. The p 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: diff --git a/src/pages/docs/livesync/postgres/index.mdx b/src/pages/docs/livesync/postgres/index.mdx index d1741f2191..b18552be87 100644 --- a/src/pages/docs/livesync/postgres/index.mdx +++ b/src/pages/docs/livesync/postgres/index.mdx @@ -34,15 +34,42 @@ By writing change events to the outbox table within the same database transactio ### Creating the rule -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). - -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. + + + +On the [Integrations](https://ably.com/accounts/any/apps/any/integrations) page of your app: + +1. Click **New Integration Rule**. +2. Choose **Postgres**. +3. Configure the [rule settings](#configure). + + + + +Create a Postgres rule using the Control API by sending a `POST` request to [`/apps/{app_id}/rules`](/docs/api/control-api): + + +```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" + } + }' +``` + + + + ### Rule configuration diff --git a/src/pages/docs/messages/annotations.mdx b/src/pages/docs/messages/annotations.mdx index 929f7cb6c5..c3ab7ca093 100644 --- a/src/pages/docs/messages/annotations.mdx +++ b/src/pages/docs/messages/annotations.mdx @@ -25,11 +25,50 @@ Annotations can be enabled for a channel or channel namespace with the *Message When message annotations are enabled, messages are [persisted](/docs/storage-history/storage#all-message-persistence) regardless of whether or not persistence is enabled, in order to support the feature. This may increase your usage since [we charge for persisting messages](https://faqs.ably.com/how-does-ably-count-messages). -1. Go to the **Settings** tab of an app in your dashboard. -3. Under [rules](/docs/channels#rules), click **Add new rule**. -4. Enter the channel name or channel namespace on which to enable message annotations. -5. Check **Message annotations, updates, deletes, and appends** to enable message annotations. -6. Click **Create rule** to save. + + + +In your app [settings](https://ably.com/accounts/any/apps/any/settings): + +1. Click **Add new rule**. +2. Enter the channel name or namespace on which to enable message annotations. +3. Check **Message annotations, updates, deletes, and appends**. +4. Click **Create rule** to save. + + + + +Create a rule with annotations enabled using the Control API by sending a `POST` request to [`/apps/{app_id}/namespaces`](/docs/api/control-api): + + +```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", + "mutableMessages": true + }' +``` + + + + + +Use the [Ably CLI](/docs/platform/tools/cli) to create a rule with annotations enabled: + + +```shell +ably apps rules create \ + --name "my-namespace" \ + --mutable-messages +``` + + +Run `ably apps rules create --help` for a full list of available options. + + + ## Annotation types diff --git a/src/pages/docs/messages/batch.mdx b/src/pages/docs/messages/batch.mdx index e5866de3c3..ab1f8bec67 100644 --- a/src/pages/docs/messages/batch.mdx +++ b/src/pages/docs/messages/batch.mdx @@ -27,15 +27,55 @@ When configuring server-side batching, you need to configure a batching interval Each batch can contain up to 200 messages by count or total data size. For example, if you have 210 messages, they will be split into two batches: one with 200 messages and another with 10 messages. If the combined data size of 200 messages exceeds the data limit, the excess will be allocated to a new batch as separate messages. -Use the following steps to configure server-side batching for a channel, or channel namespace: - -1. On your [dashboard](https://ably.com/accounts/any), select one of your apps. -2. Go to **Settings**. -3. Under [rules](/docs/channels#rules), click **Add new rule**. -4. Enter the channel name, or channel namespace to apply server-side batching to. -5. Check **Server-side batching enabled**. -6. Choose a batching interval over which to aggregate messages. -7. Click **Create rule** to save. +Use one of the following methods to configure server-side batching for a channel or channel namespace: + + + + +In your app [settings](https://ably.com/accounts/any/apps/any/settings): + +1. Click **Add new rule**. +2. Enter the channel name or namespace to apply server-side batching to. +3. Check **Server-side batching enabled**. +4. Choose a batching interval over which to aggregate messages. +5. Click **Create rule** to save. + + + + +Create a rule with server-side batching using the Control API by sending a `POST` request to [`/apps/{app_id}/namespaces`](/docs/api/control-api): + + +```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", + "batchingEnabled": true, + "batchingInterval": 100 + }' +``` + + + + + +Use the [Ably CLI](/docs/platform/tools/cli) to create a rule with server-side batching: + + +```shell +ably apps rules create \ + --name "my-namespace" \ + --batching-enabled \ + --batching-interval 100 +``` + + +Run `ably apps rules create --help` for a full list of available options. + + +