diff --git a/apps/web/app/api/customers/[id]/activity/route.ts b/apps/web/app/api/customers/[id]/activity/route.ts index 8f62872bf6..cba5de706e 100644 --- a/apps/web/app/api/customers/[id]/activity/route.ts +++ b/apps/web/app/api/customers/[id]/activity/route.ts @@ -33,7 +33,7 @@ export const GET = withWorkspace(async ({ workspace, params }) => { getEvents({ customerId: customer.id, event: "sales", - order: "desc", + sortOrder: "desc", sortBy: "timestamp", interval: "1y", page: 1, diff --git a/apps/web/app/api/links/export/route.ts b/apps/web/app/api/links/export/route.ts index cbb369b14d..9ef1b7ca85 100644 --- a/apps/web/app/api/links/export/route.ts +++ b/apps/web/app/api/links/export/route.ts @@ -81,6 +81,8 @@ export const GET = withWorkspace( }), ...(userId && { userId }), }, + + // TODO: orderBy is not currently supported orderBy: { [sort]: "desc", }, diff --git a/apps/web/app/api/links/route.ts b/apps/web/app/api/links/route.ts index 6f635622d2..e8ddfc7f51 100644 --- a/apps/web/app/api/links/route.ts +++ b/apps/web/app/api/links/route.ts @@ -19,46 +19,15 @@ import { NextResponse } from "next/server"; export const GET = withWorkspace( async ({ req, headers, workspace }) => { const searchParams = getSearchParamsWithArray(req.url); + const params = getLinksQuerySchemaExtended.parse(searchParams); - const { - domain, - tagId, - tagIds, - search, - sort, - page, - pageSize, - userId, - showArchived, - withTags, - includeUser, - includeWebhooks, - includeDashboard, - linkIds, - tenantId, - } = getLinksQuerySchemaExtended.parse(searchParams); - - if (domain) { - await getDomainOrThrow({ workspace, domain }); + if (params.domain) { + await getDomainOrThrow({ workspace, domain: params.domain }); } const response = await getLinksForWorkspace({ + ...params, workspaceId: workspace.id, - domain, - tagId, - tagIds, - search, - sort, - page, - pageSize, - userId, - showArchived, - withTags, - includeUser, - includeWebhooks, - includeDashboard, - linkIds, - tenantId, }); return NextResponse.json(response, { diff --git a/apps/web/lib/actions/partners/backfill-link-data.ts b/apps/web/lib/actions/partners/backfill-link-data.ts index 81757c3c11..393c2c5d8e 100644 --- a/apps/web/lib/actions/partners/backfill-link-data.ts +++ b/apps/web/lib/actions/partners/backfill-link-data.ts @@ -45,7 +45,7 @@ export const backfillLinkData = async ({ interval: "all", page: 1, limit: 5000, - order: "desc", + sortOrder: "desc", sortBy: "timestamp", }); diff --git a/apps/web/lib/analytics/get-events.ts b/apps/web/lib/analytics/get-events.ts index e4f22cf445..9840577848 100644 --- a/apps/web/lib/analytics/get-events.ts +++ b/apps/web/lib/analytics/get-events.ts @@ -35,6 +35,8 @@ export const getEvents = async (params: EventsFilters) => { region, country, isDemo, + order, + sortOrder, } = params; const { startDate, endDate } = getStartEndDates({ @@ -57,6 +59,11 @@ export const getEvents = async (params: EventsFilters) => { region = split[1]; } + // support legacy order param + if (order && order !== "desc") { + sortOrder = order; + } + const pipe = (isDemo ? tbDemo : tb).buildPipe({ pipe: "v2_events", parameters: eventsFilterTB, @@ -75,6 +82,7 @@ export const getEvents = async (params: EventsFilters) => { qr, country, region, + order: sortOrder, offset: (params.page - 1) * params.limit, start: startDate.toISOString().replace("T", " ").replace("Z", ""), end: endDate.toISOString().replace("T", " ").replace("Z", ""), diff --git a/apps/web/lib/analytics/types.ts b/apps/web/lib/analytics/types.ts index b78367d287..8ba642c2ea 100644 --- a/apps/web/lib/analytics/types.ts +++ b/apps/web/lib/analytics/types.ts @@ -64,6 +64,7 @@ const partnerEventsSchema = eventsQuerySchema page: true, limit: true, order: true, + sortOrder: true, sortBy: true, }) .partial(); diff --git a/apps/web/lib/api/links/get-links-for-workspace.ts b/apps/web/lib/api/links/get-links-for-workspace.ts index 4deeebfdd8..f50c5e6390 100644 --- a/apps/web/lib/api/links/get-links-for-workspace.ts +++ b/apps/web/lib/api/links/get-links-for-workspace.ts @@ -11,7 +11,9 @@ export async function getLinksForWorkspace({ tagIds, tagNames, search, - sort = "createdAt", + sort, // Deprecated + sortBy, + sortOrder, page, pageSize, userId, @@ -27,6 +29,11 @@ export async function getLinksForWorkspace({ }) { const combinedTagIds = combineTagIds({ tagId, tagIds }); + // support legacy sort param + if (sort && sort !== "createdAt") { + sortBy = sort; + } + const links = await prisma.link.findMany({ where: { projectId: workspaceId, @@ -85,7 +92,7 @@ export async function getLinksForWorkspace({ dashboard: includeDashboard, }, orderBy: { - [sort]: "desc", + [sortBy]: sortOrder, }, take: pageSize, skip: (page - 1) * pageSize, diff --git a/apps/web/lib/openapi/links/get-links.ts b/apps/web/lib/openapi/links/get-links.ts index be32f6e839..6130ff967b 100644 --- a/apps/web/lib/openapi/links/get-links.ts +++ b/apps/web/lib/openapi/links/get-links.ts @@ -1,6 +1,6 @@ import { openApiErrorResponses } from "@/lib/openapi/responses"; import z from "@/lib/zod"; -import { getLinksQuerySchema, LinkSchema } from "@/lib/zod/schemas/links"; +import { getLinksQuerySchemaBase, LinkSchema } from "@/lib/zod/schemas/links"; import { ZodOpenApiOperationObject } from "zod-openapi"; export const getLinks: ZodOpenApiOperationObject = { @@ -28,7 +28,7 @@ export const getLinks: ZodOpenApiOperationObject = { description: "Retrieve a paginated list of links for the authenticated workspace.", requestParams: { - query: getLinksQuerySchema, + query: getLinksQuerySchemaBase, }, responses: { "200": { diff --git a/apps/web/lib/zod/schemas/analytics.ts b/apps/web/lib/zod/schemas/analytics.ts index b2f37e9a4f..20bb2b0191 100644 --- a/apps/web/lib/zod/schemas/analytics.ts +++ b/apps/web/lib/zod/schemas/analytics.ts @@ -242,6 +242,12 @@ export const eventsFilterTB = analyticsFilterTB }), ); +const sortOrder = z + .enum(["asc", "desc"]) + .default("desc") + .optional() + .describe("The sort order. The default is `desc`."); + export const eventsQuerySchema = analyticsQuerySchema .omit({ groupBy: true }) .extend({ @@ -259,6 +265,13 @@ export const eventsQuerySchema = analyticsQuerySchema ), page: z.coerce.number().default(1), limit: z.coerce.number().default(PAGINATION_LIMIT), - order: z.enum(["asc", "desc"]).default("desc"), - sortBy: z.enum(["timestamp"]).default("timestamp"), + sortOrder, + sortBy: z + .enum(["timestamp"]) + .optional() + .default("timestamp") + .describe("The field to sort the events by. The default is `timestamp`."), + order: sortOrder + .describe("DEPRECATED. Use `sortOrder` instead.") + .openapi({ deprecated: true }), }); diff --git a/apps/web/lib/zod/schemas/links.ts b/apps/web/lib/zod/schemas/links.ts index 1d530d354d..bb067ab3c2 100644 --- a/apps/web/lib/zod/schemas/links.ts +++ b/apps/web/lib/zod/schemas/links.ts @@ -78,15 +78,23 @@ const LinksQuerySchema = z.object({ .openapi({ deprecated: true }), }); -export const getLinksQuerySchema = LinksQuerySchema.merge( +const sortBy = z + .enum(["createdAt", "clicks", "saleAmount", "lastClicked"]) + .optional() + .default("createdAt") + .describe("The field to sort the links by. The default is `createdAt`."); + +export const getLinksQuerySchemaBase = LinksQuerySchema.merge( z.object({ - sort: z - .enum(["createdAt", "clicks", "lastClicked"]) + sortBy, + sortOrder: z + .enum(["asc", "desc"]) .optional() - .default("createdAt") - .describe( - "The field to sort the links by. The default is `createdAt`, and sort order is always descending.", - ), + .default("desc") + .describe("The sort order. The default is `desc`."), + sort: sortBy + .openapi({ deprecated: true }) + .describe("DEPRECATED. Use `sortBy` instead."), }), ).merge(getPaginationQuerySchema({ pageSize: 100 })); @@ -99,7 +107,7 @@ export const getLinksCountQuerySchema = LinksQuerySchema.merge( }), ); -export const linksExportQuerySchema = getLinksQuerySchema +export const linksExportQuerySchema = getLinksQuerySchemaBase .omit({ page: true, pageSize: true }) .merge( z.object({ @@ -603,7 +611,7 @@ export const getLinkInfoQuerySchema = domainKeySchema.partial().merge( }), ); -export const getLinksQuerySchemaExtended = getLinksQuerySchema.merge( +export const getLinksQuerySchemaExtended = getLinksQuerySchemaBase.merge( z.object({ // Only Dub UI uses the following query parameters includeUser: booleanQuerySchema.default("false"), diff --git a/apps/web/scripts/backfill-missing-sales.ts b/apps/web/scripts/backfill-missing-sales.ts index b767c9846a..d2b9d963c7 100644 --- a/apps/web/scripts/backfill-missing-sales.ts +++ b/apps/web/scripts/backfill-missing-sales.ts @@ -53,7 +53,7 @@ async function main() { interval: "all", page: 1, limit: 1000, - order: "desc", + sortOrder: "desc", sortBy: "timestamp", })) as unknown as SaleEvent[]; diff --git a/apps/web/scripts/backfill-sales.ts b/apps/web/scripts/backfill-sales.ts index ee9a6b222f..974451fd5a 100644 --- a/apps/web/scripts/backfill-sales.ts +++ b/apps/web/scripts/backfill-sales.ts @@ -41,7 +41,7 @@ async function main() { interval: "all", page: 1, limit: 5000, - order: "desc", + sortOrder: "desc", sortBy: "timestamp", }); diff --git a/apps/web/ui/analytics/events/events-table.tsx b/apps/web/ui/analytics/events/events-table.tsx index c063d9c0de..1cbfe17736 100644 --- a/apps/web/ui/analytics/events/events-table.tsx +++ b/apps/web/ui/analytics/events/events-table.tsx @@ -65,8 +65,8 @@ export default function EventsTable({ const { columnVisibility, setColumnVisibility } = useColumnVisibility(); - const sortBy = searchParams.get("sort") || "timestamp"; - const order = searchParams.get("order") === "asc" ? "asc" : "desc"; + const sortBy = searchParams.get("sortBy") || "timestamp"; + const sortOrder = searchParams.get("sortOrder") === "asc" ? "asc" : "desc"; const columns = useMemo[]>( () => @@ -440,9 +440,9 @@ export default function EventsTable({ event: tab, page: pagination.pageIndex.toString(), sortBy, - order, + sortOrder, }).toString(), - [originalQueryString, tab, pagination, sortBy, order], + [originalQueryString, tab, pagination, sortBy, sortOrder], ); // Update export query string @@ -482,13 +482,13 @@ export default function EventsTable({ columnVisibility: columnVisibility[tab], onColumnVisibilityChange: (args) => setColumnVisibility(tab, args), sortableColumns: ["timestamp"], - sortBy: sortBy, - sortOrder: order, + sortBy, + sortOrder, onSortChange: ({ sortBy, sortOrder }) => queryParams({ set: { - ...(sortBy && { sort: sortBy }), - ...(sortOrder && { order: sortOrder }), + ...(sortBy && { sortBy }), + ...(sortOrder && { sortOrder }), }, }), columnPinning: { right: ["menu"] }, diff --git a/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx b/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx index a2ed30981d..8ab08967e3 100644 --- a/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx +++ b/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx @@ -264,7 +264,9 @@ export function AppSidebarNav({ currentArea={currentArea} data={{ slug: slug || "", - queryString: getQueryString(), + queryString: getQueryString(undefined, { + ignore: ["sortBy", "sortOrder"], + }), flags, programs, session: session || undefined, diff --git a/apps/web/ui/links/link-sort.tsx b/apps/web/ui/links/link-sort.tsx index 804351de17..7e6a7f3b35 100644 --- a/apps/web/ui/links/link-sort.tsx +++ b/apps/web/ui/links/link-sort.tsx @@ -9,9 +9,9 @@ export default function LinkSort() { const [openPopover, setOpenPopover] = useState(false); - const { sort: sortSlug, setSort } = useContext(LinksDisplayContext); + const { sortBy, setSort } = useContext(LinksDisplayContext); const selectedSort = - sortOptions.find((s) => s.slug === sortSlug) ?? sortOptions[0]; + sortOptions.find((s) => s.slug === sortBy) ?? sortOptions[0]; return ( } /> - {sortSlug === slug && ( + {sortBy === slug && (