Skip to content

Commit 91d4d2f

Browse files
fix(changelog): address review — body via file path, ms-precision timeline, honest workflow label
1 parent a07d962 commit 91d4d2f

12 files changed

Lines changed: 379 additions & 350 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/changelog/changelog.tsx‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import { useState } from 'react'
4-
import { Chip } from '@sim/emcn'
4+
import { Chip, cn, scrollFadeAttributes, scrollFadeClass, useScrollEdges } from '@sim/emcn'
55
import { Rss } from '@sim/emcn/icons'
66
import { EmptyState } from '@/components/empty-state/empty-state'
77
import type { ChangelogRelease } from '@/lib/api/contracts/changelog'
@@ -12,6 +12,7 @@ import {
1212
import ChangelogLoading from '@/app/workspace/[workspaceId]/changelog/loading'
1313
import { Resource } from '@/app/workspace/[workspaceId]/components/resource/resource'
1414
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
15+
import { PermissionAccessBoundary } from '@/ee/access-requests/components/permission-access-boundary'
1516
import { useChangelogReleases } from '@/hooks/queries/changelog'
1617

1718
interface ChangelogProps {
@@ -20,9 +21,19 @@ interface ChangelogProps {
2021

2122
/** Every release Sim published in this workspace, newest first. */
2223
export function Changelog({ workspaceId }: ChangelogProps) {
24+
return (
25+
<PermissionAccessBoundary configKey='hideFilesTab'>
26+
<ChangelogContent workspaceId={workspaceId} />
27+
</PermissionAccessBoundary>
28+
)
29+
}
30+
31+
function ChangelogContent({ workspaceId }: ChangelogProps) {
2332
const canEdit = useUserPermissionsContext().canEdit === true
2433
const query = useChangelogReleases(workspaceId)
2534
const [editing, setEditing] = useState<ChangelogRelease | null>(null)
35+
const [scrollElement, setScrollElement] = useState<HTMLDivElement | null>(null)
36+
const edges = useScrollEdges(scrollElement)
2637

2738
if (query.isPending) return <ChangelogLoading />
2839
const releases = query.data?.pages.flatMap((page) => page.releases) ?? []
@@ -41,7 +52,11 @@ export function Changelog({ workspaceId }: ChangelogProps) {
4152
description='Sim publishes a release here when it ships changes to this workspace.'
4253
/>
4354
) : (
44-
<div className='@container/changelog min-h-0 flex-1 overflow-y-auto'>
55+
<div
56+
ref={setScrollElement}
57+
className={cn('@container/changelog min-h-0 flex-1 overflow-y-auto', scrollFadeClass)}
58+
{...scrollFadeAttributes(edges)}
59+
>
4560
<div className='mx-auto flex max-w-[960px] flex-col gap-12 px-8 py-10'>
4661
{releases.map((release) => (
4762
<ReleaseEntry

‎apps/sim/app/workspace/[workspaceId]/changelog/components/release-workflows/release-workflows.tsx‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import { useState } from 'react'
4-
import { ChipModal, ChipModalBody, ChipModalHeader } from '@sim/emcn'
4+
import { ChipModal, ChipModalBody, ChipModalHeader, OverflowText } from '@sim/emcn'
55
import Link from 'next/link'
66
import type { ChangelogReleaseWorkflow } from '@/lib/api/contracts/changelog'
77

@@ -10,11 +10,12 @@ interface ReleaseWorkflowsProps {
1010
workflows: ChangelogReleaseWorkflow[]
1111
}
1212

13-
/** "N workflows deployed", opening the list of each workflow and the deployment it shipped in. */
13+
/** "N workflows deployed" (or "changed" when some shipped no deployment), opening each workflow's version. */
1414
export function ReleaseWorkflows({ workspaceId, workflows }: ReleaseWorkflowsProps) {
1515
const [open, setOpen] = useState(false)
1616
if (workflows.length === 0) return null
17-
const label = `${workflows.length} ${workflows.length === 1 ? 'workflow' : 'workflows'} deployed`
17+
const allDeployed = workflows.every((workflow) => workflow.deploymentVersion !== null)
18+
const label = `${workflows.length} ${workflows.length === 1 ? 'workflow' : 'workflows'} ${allDeployed ? 'deployed' : 'changed'}`
1819

1920
return (
2021
<>
@@ -36,9 +37,9 @@ export function ReleaseWorkflows({ workspaceId, workflows }: ReleaseWorkflowsPro
3637
>
3738
<Link
3839
href={`/workspace/${workspaceId}/w/${workflow.id}`}
39-
className='min-w-0 flex-1 truncate text-[var(--text-body)] text-sm underline-offset-4 hover:underline'
40+
className='flex min-w-0 flex-1 underline-offset-4 hover:underline'
4041
>
41-
{workflow.name}
42+
<OverflowText label={workflow.name} className='text-[var(--text-body)] text-sm' />
4243
</Link>
4344
<span className='shrink-0 text-[var(--text-muted)] text-small'>
4445
{workflow.deploymentVersion === null

‎apps/sim/hooks/queries/workspace-files.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,9 @@ export function useUpdateWorkspaceFileContent() {
636636
queryClient.invalidateQueries({
637637
queryKey: workspaceFilesKeys.workspaceLists(variables.workspaceId),
638638
})
639+
queryClient.invalidateQueries({
640+
queryKey: workspaceFilesKeys.record(variables.workspaceId, variables.fileId),
641+
})
639642
queryClient.invalidateQueries({ queryKey: workspaceFilesKeys.storageInfo() })
640643
},
641644
onError: (error) => {

‎apps/sim/lib/changelog/application/changelog.ts‎

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { requirePrincipalSubjectUserId } from '@sim/auth/principal'
33
import { getPostgresErrorCode } from '@sim/utils/errors'
44
import { generateId } from '@sim/utils/id'
55
import { changelogOperations } from '@/lib/changelog/application/operations'
6-
import { assertChangelogBody, MAX_CHANGELOG_BODY_BYTES } from '@/lib/changelog/body'
6+
import { assertChangelogBody } from '@/lib/changelog/body'
77
import { requireChangelogEnabled } from '@/lib/changelog/feature-flag'
88
import { changelogFilePath } from '@/lib/changelog/paths'
99
import {
@@ -27,11 +27,7 @@ import {
2727
} from '@/lib/changelog/version'
2828
import { defineAuthorizedWorkspaceUseCase } from '@/lib/core/application'
2929
import { OrchestrationError } from '@/lib/core/orchestration/types'
30-
import {
31-
createOwnedWorkspaceFile,
32-
fetchWorkspaceFileBuffer,
33-
getWorkspaceFile,
34-
} from '@/lib/uploads/contexts/workspace/workspace-file-manager'
30+
import { createOwnedWorkspaceFile } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
3531
import { resolveActiveWorkspaceApplicationContext } from '@/lib/workspaces/application/workspace-context'
3632

3733
const authorizationOptions = {
@@ -140,13 +136,6 @@ async function assertChangesInWorkspace(
140136
)
141137
}
142138

143-
async function readReleaseBody(row: ChangelogReleaseRow): Promise<string> {
144-
const file = await getWorkspaceFile(row.workspaceId, row.bodyFileId, { includeOwnedFiles: true })
145-
if (!file) throw new Error(`Release ${row.id} has no body file ${row.bodyFileId}`)
146-
const buffer = await fetchWorkspaceFileBuffer(file, { maxBytes: MAX_CHANGELOG_BODY_BYTES })
147-
return buffer.toString('utf-8')
148-
}
149-
150139
const resolveWorkspace = ({ input }: { input: { workspaceId: string } }) =>
151140
resolveActiveWorkspaceApplicationContext(input.workspaceId)
152141

@@ -168,7 +157,7 @@ export const listChangelogReleases = defineAuthorizedWorkspaceUseCase({
168157
},
169158
})
170159

171-
/** One release with its markdown body, for Sim to read before it edits. */
160+
/** One release; Sim reads and edits its body at `release.path` with the file tools. */
172161
export const getChangelogRelease = defineAuthorizedWorkspaceUseCase({
173162
operation: changelogOperations.read,
174163
resolveContext: ({ input }: { input: { workspaceId: string; releaseId: string } }) =>
@@ -179,7 +168,7 @@ export const getChangelogRelease = defineAuthorizedWorkspaceUseCase({
179168
const row = await getRelease(context.workspaceId, input.releaseId)
180169
if (!row) throw new OrchestrationError('not_found', 'Release not found')
181170
const [release] = await presentReleases([row])
182-
return { release, body: await readReleaseBody(row) }
171+
return { release }
183172
},
184173
})
185174

@@ -276,6 +265,16 @@ export const updateChangelogRelease = defineAuthorizedWorkspaceUseCase({
276265
authorizeResource: ({ context }) => requireChangelogEnabled(context.workspaceOrganizationId),
277266
async execute({ input, context, principal }) {
278267
const revision = parseRevision(input.expectedRevision)
268+
if (
269+
input.title === undefined &&
270+
input.bumpReason === undefined &&
271+
input.version === undefined &&
272+
input.changes === undefined
273+
)
274+
throw new OrchestrationError(
275+
'validation',
276+
'Pass at least one of title, bumpReason, version, or changes'
277+
)
279278
const version = input.version === undefined ? undefined : parseReleaseVersion(input.version)
280279
const existing = await getRelease(context.workspaceId, input.releaseId)
281280
if (!existing) throw new OrchestrationError('not_found', 'Release not found')
@@ -294,8 +293,7 @@ export const updateChangelogRelease = defineAuthorizedWorkspaceUseCase({
294293
'conflict',
295294
'The release changed after it was read; read it again and reapply your edit'
296295
)
297-
const [release] = await presentReleases([updated])
298-
return { release }
296+
return { release: presentRelease(updated.row, updated.changes) }
299297
} catch (error) {
300298
if (getPostgresErrorCode(error) === '23505' && version)
301299
throw new OrchestrationError(

‎apps/sim/lib/changelog/body.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { isUtf8 } from 'node:buffer'
22
import { OrchestrationError } from '@/lib/core/orchestration/types'
33

44
/** A release body is prose with a few embeds; anything larger is not a release note. */
5-
export const MAX_CHANGELOG_BODY_BYTES = 256 * 1024
5+
const MAX_CHANGELOG_BODY_BYTES = 256 * 1024
66

77
export function assertChangelogBody(content: Buffer): void {
88
if (content.length > MAX_CHANGELOG_BODY_BYTES) {

‎apps/sim/lib/changelog/repository.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,12 @@ export async function listReleases(
139139
}
140140

141141
export async function listChangesByRelease(
142-
releaseIds: string[]
142+
releaseIds: string[],
143+
executor: DbOrTx = db
143144
): Promise<Map<string, ChangelogChangeView[]>> {
144145
const byRelease = new Map<string, ChangelogChangeView[]>()
145146
if (releaseIds.length === 0) return byRelease
146-
const rows = await db
147+
const rows = await executor
147148
.select({
148149
releaseId: changelogChange.releaseId,
149150
id: changelogChange.id,
@@ -178,15 +179,15 @@ export interface ReleaseFieldsUpdate {
178179

179180
/**
180181
* Applies a field update, and replaces the changes when given, only while the revision still
181-
* matches; null when it does not.
182+
* matches; null when it does not. Returns the row with the changes of that same revision.
182183
*/
183184
export async function updateRelease(
184185
releaseId: string,
185186
expectedRevision: number,
186187
userId: string,
187188
fields: ReleaseFieldsUpdate,
188189
changes: ChangelogChangeInput[] | undefined
189-
): Promise<ChangelogReleaseRow | null> {
190+
): Promise<{ row: ChangelogReleaseRow; changes: ChangelogChangeView[] } | null> {
190191
return db.transaction(async (tx) => {
191192
const [row] = await tx
192193
.update(changelogRelease)
@@ -213,7 +214,8 @@ export async function updateRelease(
213214
await tx.delete(changelogChange).where(eq(changelogChange.releaseId, releaseId))
214215
await insertChanges(tx, releaseId, changes)
215216
}
216-
return row
217+
const changesByRelease = await listChangesByRelease([releaseId], tx)
218+
return { row, changes: changesByRelease.get(releaseId) ?? [] }
217219
})
218220
}
219221

‎apps/sim/lib/changelog/version.ts‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,17 @@ export function bumpReleaseVersion(
4040
bump: VersionBump
4141
): ReleaseVersion {
4242
if (!highest) return FIRST_VERSION
43-
if (bump === 'major') return { major: highest.major + 1, minor: 0, patch: 0 }
44-
if (bump === 'minor') return { major: highest.major, minor: highest.minor + 1, patch: 0 }
45-
return { ...highest, patch: highest.patch + 1 }
43+
const next =
44+
bump === 'major'
45+
? { major: highest.major + 1, minor: 0, patch: 0 }
46+
: bump === 'minor'
47+
? { major: highest.major, minor: highest.minor + 1, patch: 0 }
48+
: { ...highest, patch: highest.patch + 1 }
49+
if (Object.values(next).some((component) => component > MAX_COMPONENT)) {
50+
throw new OrchestrationError(
51+
'validation',
52+
`A ${bump} bump from ${formatReleaseVersion(highest)} is out of range`
53+
)
54+
}
55+
return next
4656
}

‎packages/db/migrations/0404_changelog.sql‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ CREATE TABLE "changelog_release" (
1818
"bump_reason" text NOT NULL,
1919
"body_file_id" text NOT NULL,
2020
"revision" integer DEFAULT 1 NOT NULL,
21-
"published_at" timestamp DEFAULT now() NOT NULL,
21+
"published_at" timestamp (3) DEFAULT now() NOT NULL,
2222
"created_by" text,
2323
"updated_by" text,
2424
"created_at" timestamp DEFAULT now() NOT NULL,

‎packages/db/migrations/meta/0404_snapshot.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"id": "fcb8bd21-b9e1-4f31-879b-16a061292e81",
2+
"id": "9340200a-3359-4467-9bff-74f740d10120",
33
"prevId": "44e8985f-7e13-4c8f-84d7-be85dc491ba6",
44
"version": "7",
55
"dialect": "postgresql",
@@ -1447,7 +1447,7 @@
14471447
},
14481448
"published_at": {
14491449
"name": "published_at",
1450-
"type": "timestamp",
1450+
"type": "timestamp (3)",
14511451
"primaryKey": false,
14521452
"notNull": true,
14531453
"default": "now()"

‎packages/db/migrations/meta/_journal.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2826,7 +2826,7 @@
28262826
{
28272827
"idx": 404,
28282828
"version": "7",
2829-
"when": 1791567251419,
2829+
"when": 1791573858975,
28302830
"tag": "0404_changelog",
28312831
"breakpoints": true
28322832
}

0 commit comments

Comments
 (0)