Skip to content

Commit 3626d7a

Browse files
committed
feat(sim-links): real-HTTP E2E suite, docs, and field-level name conflicts
1 parent c964348 commit 3626d7a

8 files changed

Lines changed: 414 additions & 3 deletions

File tree

‎.github/scripts/http-e2e.sh‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ case "$group" in
144144
export INTERNAL_API_SECRET=scim-http-ci-local-secret-at-least-32-characters
145145
export DB_TX_TRIPWIRE=throw
146146
export NEXT_PUBLIC_CHAT_DISABLED=true
147+
export SIM_LINKS=true
147148
start_app scim 3017 SCIM record-http-status
148149
SCIM_E2E_BASE_URL="$local_app_url" \
149150
SCIM_E2E_DATABASE_URL="$DATABASE_URL" \
@@ -155,6 +156,11 @@ case "$group" in
155156
VERSION_COMPARE_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
156157
VERSION_COMPARE_E2E_REPORT_PATH="$report_dir/version-compare-http-report.json" \
157158
bun run test:workflow-version-compare:e2e
159+
SIM_LINKS_E2E_BASE_URL="$local_app_url" \
160+
SIM_LINKS_E2E_DATABASE_URL="$DATABASE_URL" \
161+
SIM_LINKS_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
162+
SIM_LINKS_E2E_REPORT_PATH="$report_dir/sim-links-e2e-report.json" \
163+
bun run test:sim-links:e2e
158164
;;
159165

160166
# The search suites serve their own fixtures in-process and need no app. They share this group

‎apps/docs/content/docs/platform/enterprise/audit-logs.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Events use identifiers such as `resource.action`. These categories are represent
101101
| API keys | `api_key.created`, `api_key.revoked` |
102102
| Credentials | `credential.created`, `credential.deleted`, `oauth.disconnected` |
103103
| Organization | `organization.updated`, `org_member.added`, `org_member.role_changed`, `organization.sso_provider.updated` |
104+
| Sim links | `sim_link.created`, `sim_link.updated`, `sim_link.deleted` |
104105
| Exports and drains | `audit_logs.exported`, `data_drain.created`, `data_drain.updated`, `data_drain.ran`, `data_drain.tested` |
105106

106107
Authentication failures and password-reset requests use an anonymous actor and record the target account separately. Impersonation and administrative session revocations identify the operator and target organization. Table-row mutations record summaries and counts without copying row contents.

‎apps/docs/content/docs/search/meta.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"quickstart",
66
"find-content",
77
"ask-questions",
8+
"sim-links",
89
"troubleshooting",
910
"---Connect sources---",
1011
"connect-your-account",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
title: Sim Links
3+
description: Short sim/ links that take everyone in your organization to the right page
4+
---
5+
6+
Sim Links are short, memorable names for the pages your team opens often, like `sim/handbook` for the employee handbook or `sim/okta` for your sign-in portal. Anyone in the organization can open them, with or without a Search seat. They work like go links in other tools.
7+
8+
Open **Sim Links** in the organization sidebar to see every link in your organization. Search by name, destination, or description.
9+
10+
## Open a link
11+
12+
Go to `https://sim.ai/sim/<name>`, for example `https://sim.ai/sim/handbook`. On a self-hosted deployment, use your Sim address instead of `sim.ai`. Sim sends you to the link's destination. If you aren't signed in, Sim asks you to sign in first, then continues to the link.
13+
14+
Names ignore case and punctuation, so `sim/Team-Handbook`, `sim/team_handbook`, and `sim/teamhandbook` all open the same link. The destination still applies its own permissions.
15+
16+
If nobody has created the link yet, Sim offers to create it with that name.
17+
18+
## Create a link
19+
20+
1. Open **Sim Links** and select **New link**.
21+
2. Enter the **Destination URL**, a full `https://` address.
22+
3. Enter a **Name**. Use letters, numbers, and `. : @ & ~ - _`.
23+
4. Optionally add a **Description** so teammates know what the link is for.
24+
5. Select **Create**.
25+
26+
Select **Copy link** from a link's menu to share it.
27+
28+
## Add values to a link
29+
30+
Anything after the name is added to the destination. If `sim/docs` opens `https://docs.example.com`, then `sim/docs/billing/invoices` opens `https://docs.example.com/billing/invoices`.
31+
32+
To place a value somewhere else, put `{*}` in the destination URL:
33+
34+
| Destination URL | You open | Sim opens |
35+
| --- | --- | --- |
36+
| `https://jira.example.com/browse/PROD-{*}` | `sim/jira/123` | `https://jira.example.com/browse/PROD-123` |
37+
| `https://wiki.example.com/search?q={*}` | `sim/wiki/expense policy` | `https://wiki.example.com/search?q=expense%20policy` |
38+
| `https://github.com/acme/{*}/issues/{*}` | `sim/issues/api/42` | `https://github.com/acme/api/issues/42` |
39+
40+
Each value fills the next `{*}` in order, and the last `{*}` takes everything that remains. A link with `{*}` can also have a **Default destination**, which opens when you use the link without a value. `{*}` can't replace the domain.
41+
42+
## Control access
43+
44+
Under **Access**, the creator of a link can choose:
45+
46+
- **Unlist**: hides the link from the Sim Links page for everyone except the creator and organization admins. Anyone who knows the name can still open it.
47+
- **Restrict editing**: only the creator and organization admins can edit or delete the link. Everyone can still open it.
48+
49+
Without restrictions, any member can edit or delete a link. Only the creator and organization admins can change its access settings. Creating, editing, and deleting links is recorded in your organization's [audit logs](/platform/enterprise/audit-logs).
50+
51+
## Related docs
52+
53+
- [Find content](/search/find-content)

‎apps/sim/app/o/[organizationId]/links/sim-link-modal.tsx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ChipModalFooter,
1111
ChipModalHeader,
1212
} from '@sim/emcn'
13+
import { isApiClientError } from '@/lib/api/client/errors'
1314
import type { SimLink, SimLinkBody } from '@/lib/api/contracts/sim-links'
1415
import {
1516
isVariableSimLinkUrl,
@@ -45,6 +46,7 @@ function draftOf(link: SimLink | undefined, initialName = ''): SimLinkBody {
4546
/** Mount per link (keyed) so each open starts from that link's saved fields. */
4647
export function SimLinkModal({ organizationId, onClose, link, initialName }: SimLinkModalProps) {
4748
const mutation = useSaveSimLink(organizationId)
49+
const nameTaken = isApiClientError(mutation.error) && mutation.error.status === 409
4850
const [draft, setDraft] = useState(() => draftOf(link, initialName))
4951
const [errors, setErrors] = useState<Partial<Record<Field, string>>>({})
5052
const variable = isVariableSimLinkUrl(draft.url)
@@ -55,6 +57,7 @@ export function SimLinkModal({ organizationId, onClose, link, initialName }: Sim
5557
const change = <K extends keyof SimLinkBody>(field: K, value: SimLinkBody[K]) => {
5658
setDraft((current) => ({ ...current, [field]: value }))
5759
setErrors((current) => ({ ...current, [field]: undefined }))
60+
if (mutation.isError) mutation.reset()
5861
}
5962

6063
const save = () => {
@@ -119,7 +122,7 @@ export function SimLinkModal({ organizationId, onClose, link, initialName }: Sim
119122
maxLength={SIM_LINK_NAME_MAX_LENGTH}
120123
value={draft.name}
121124
onChange={(value) => change('name', value)}
122-
error={errors.name}
125+
error={errors.name ?? (nameTaken ? mutation.error?.message : undefined)}
123126
hint='Letters, numbers, and . : @ & ~ - _. Case and punctuation are ignored, so sim/Q4-goals is sim/q4goals.'
124127
/>
125128
<ChipModalField
@@ -165,7 +168,7 @@ export function SimLinkModal({ organizationId, onClose, link, initialName }: Sim
165168
</label>
166169
</div>
167170
</ChipModalField>
168-
<ChipModalError>{mutation.error?.message}</ChipModalError>
171+
<ChipModalError>{nameTaken ? undefined : mutation.error?.message}</ChipModalError>
169172
</ChipModalBody>
170173
<ChipModalFooter
171174
onCancel={onClose}

‎apps/sim/app/o/[organizationId]/links/sim-links.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export function SimLinks({ organizationId }: SimLinksProps) {
6565
return (
6666
<OrganizationPage
6767
title='Sim Links'
68-
description='Short links for your organization. Open sim/name in your browser to go to its destination.'
68+
description='Short, shareable links that take your organization straight to the right page.'
6969
searchMode='expanded'
7070
searchPlaceholder='Search Sim links'
7171
action={

‎apps/sim/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"start": "next start",
2424
"test": "vitest run",
2525
"test:scim:e2e": "bun run scripts/test-scim-e2e.ts",
26+
"test:sim-links:e2e": "bun run scripts/test-sim-links-e2e.ts",
2627
"test:browser-extension:e2e": "bun --no-env-file scripts/test-browser-extension-e2e.ts",
2728
"test:isolated-vm-remote:e2e": "bun run scripts/test-isolated-vm-remote-e2e.ts",
2829
"test:mobile:e2e": "bun --no-env-file scripts/test-mobile-e2e.ts",

0 commit comments

Comments
 (0)