@open-mercato/core 0.7.1-develop.7106.1.53fe9dd1b1 → 0.7.1-develop.7112.1.d486e6f1b2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/users/resend-invite/route.js +1 -1
- package/dist/modules/auth/api/users/resend-invite/route.js.map +2 -2
- package/dist/modules/business_rules/backend/logs/[id]/page.js +3 -9
- package/dist/modules/business_rules/backend/logs/[id]/page.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/[id]/page.js +3 -9
- package/dist/modules/business_rules/backend/rules/[id]/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/[id]/page.js +3 -9
- package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +6 -4
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivitiesSection.js +4 -12
- package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/authorUserLookup.js +46 -0
- package/dist/modules/customers/components/detail/authorUserLookup.js.map +7 -0
- package/dist/modules/query_index/lib/engine.js +2 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +3 -9
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.js +3 -9
- package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/users/resend-invite/route.ts +1 -1
- package/src/modules/business_rules/backend/logs/[id]/page.tsx +3 -11
- package/src/modules/business_rules/backend/rules/[id]/page.tsx +3 -11
- package/src/modules/business_rules/backend/sets/[id]/page.tsx +3 -11
- package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +7 -5
- package/src/modules/communication_channels/i18n/de.json +1 -0
- package/src/modules/communication_channels/i18n/en.json +1 -0
- package/src/modules/communication_channels/i18n/es.json +1 -0
- package/src/modules/communication_channels/i18n/ko.json +1 -0
- package/src/modules/communication_channels/i18n/pl.json +1 -0
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +1 -1
- package/src/modules/customers/components/detail/ActivitiesSection.tsx +8 -18
- package/src/modules/customers/components/detail/authorUserLookup.ts +85 -0
- package/src/modules/query_index/lib/engine.ts +9 -0
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +3 -11
- package/src/modules/workflows/backend/events/[id]/page.tsx +3 -12
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
4
|
+
import { MAX_USER_LOOKUP_IDS } from '@open-mercato/core/modules/auth/lib/userIdFilter'
|
|
5
|
+
|
|
6
|
+
type AuthUserItem = {
|
|
7
|
+
id?: unknown
|
|
8
|
+
name?: unknown
|
|
9
|
+
display_name?: unknown
|
|
10
|
+
email?: unknown
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Imported rather than restated: `parseIdsParam` slices past the route's cap without complaining,
|
|
14
|
+
// so a client batch larger than the server accepts loses the overflow ids silently.
|
|
15
|
+
const MAX_IDS_PER_LOOKUP = MAX_USER_LOOKUP_IDS
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The outcome of a batch lookup. `resolvedIds` lists the ids the server actually answered for —
|
|
19
|
+
* an id that came back without a row (deleted user) still counts as resolved, an id whose request
|
|
20
|
+
* failed does not. Callers cache on `resolvedIds`, so a transient failure is retried rather than
|
|
21
|
+
* remembered as a permanent blank.
|
|
22
|
+
*/
|
|
23
|
+
export type AuthorUserLookup = {
|
|
24
|
+
names: Map<string, string>
|
|
25
|
+
resolvedIds: string[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toName(item: AuthUserItem | null | undefined): [string, string] | null {
|
|
29
|
+
if (!item || typeof item.id !== 'string' || !item.id.trim()) return null
|
|
30
|
+
const name = typeof item.name === 'string' && item.name.trim() ? item.name.trim() : null
|
|
31
|
+
const displayName = typeof item.display_name === 'string' && item.display_name.trim()
|
|
32
|
+
? item.display_name.trim()
|
|
33
|
+
: null
|
|
34
|
+
const email = typeof item.email === 'string' && item.email.trim() ? item.email.trim() : null
|
|
35
|
+
const label = name ?? displayName ?? email
|
|
36
|
+
return label ? [item.id.trim(), label] : null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// A viewer of a customer record does not necessarily hold `auth.users.list`, so this lookup must be
|
|
40
|
+
// allowed to fail: `x-om-forbidden-redirect: 0` keeps a 403 from bouncing the whole detail page to
|
|
41
|
+
// /login, and `null` signals that the call itself failed. Callers must not confuse that with a
|
|
42
|
+
// successful call that matched nobody — caching the former would remember a transient error forever.
|
|
43
|
+
async function fetchBatch(ids: string[], signal?: AbortSignal): Promise<AuthUserItem[] | null> {
|
|
44
|
+
const params = new URLSearchParams()
|
|
45
|
+
params.set('page', '1')
|
|
46
|
+
// The route's pageSize defaults to 50, so a batch larger than that would be truncated by
|
|
47
|
+
// pagination even after it survived the `?ids=` cap.
|
|
48
|
+
params.set('pageSize', String(ids.length))
|
|
49
|
+
// URLSearchParams encodes on toString(); pre-encoding here would double-escape the commas.
|
|
50
|
+
params.set('ids', ids.join(','))
|
|
51
|
+
const call = await apiCall<{ items?: AuthUserItem[] }>(
|
|
52
|
+
`/api/auth/users?${params.toString()}`,
|
|
53
|
+
{ headers: { 'x-om-forbidden-redirect': '0' }, signal },
|
|
54
|
+
{ fallback: null },
|
|
55
|
+
).catch(() => null)
|
|
56
|
+
if (!call || !call.ok) return null
|
|
57
|
+
return call.result?.items ?? []
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Batch id → display-name resolution for activity authors already on screen, so an author whose
|
|
62
|
+
* name was not denormalized onto the activity still renders a name instead of a bare UUID.
|
|
63
|
+
*/
|
|
64
|
+
export async function resolveAuthorUserNames(
|
|
65
|
+
ids: readonly string[],
|
|
66
|
+
signal?: AbortSignal,
|
|
67
|
+
): Promise<AuthorUserLookup> {
|
|
68
|
+
const unique = Array.from(new Set(ids.map((id) => id.trim()).filter(Boolean)))
|
|
69
|
+
const names = new Map<string, string>()
|
|
70
|
+
const resolvedIds: string[] = []
|
|
71
|
+
for (let offset = 0; offset < unique.length; offset += MAX_IDS_PER_LOOKUP) {
|
|
72
|
+
if (signal?.aborted) break
|
|
73
|
+
const batch = unique.slice(offset, offset + MAX_IDS_PER_LOOKUP)
|
|
74
|
+
const items = await fetchBatch(batch, signal)
|
|
75
|
+
// One failed batch must not cost the batches that did answer, and its ids stay unresolved so
|
|
76
|
+
// the next render retries them.
|
|
77
|
+
if (items === null) continue
|
|
78
|
+
for (const item of items) {
|
|
79
|
+
const entry = toName(item)
|
|
80
|
+
if (entry) names.set(entry[0], entry[1])
|
|
81
|
+
}
|
|
82
|
+
resolvedIds.push(...batch)
|
|
83
|
+
}
|
|
84
|
+
return { names, resolvedIds }
|
|
85
|
+
}
|
|
@@ -352,10 +352,19 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
352
352
|
|
|
353
353
|
const normalizedFilters = normalizeFilters(opts.filters)
|
|
354
354
|
const cfFilters = normalizedFilters.filter((filter) => filter.field.startsWith('cf:') || filter.field.startsWith('l10n:'))
|
|
355
|
+
// `applySort` orders `cf:` sorts off the left-joined index doc, so a
|
|
356
|
+
// sort-only custom-field query depends on the index just as much as a cf
|
|
357
|
+
// filter does — without this disjunct it would skip the coverage guards
|
|
358
|
+
// below and silently order by a NULL expression on an empty index (#5521).
|
|
359
|
+
// Only `cf:` qualifies: neither engine ever applies an `l10n:` ordering —
|
|
360
|
+
// both drop it while resolving sorts — so counting it here would pay the
|
|
361
|
+
// coverage probes and the ORM diversion for an ordering that is discarded.
|
|
362
|
+
const hasCfSort = (opts.sort || []).some((sort) => String(sort.field).startsWith('cf:'))
|
|
355
363
|
const coverageScope = this.resolveCoverageSnapshotScope(opts)
|
|
356
364
|
const wantsCf = (
|
|
357
365
|
(opts.fields || []).some((field) => typeof field === 'string' && (field.startsWith('cf:') || field.startsWith('l10n:'))) ||
|
|
358
366
|
cfFilters.length > 0 ||
|
|
367
|
+
hasCfSort ||
|
|
359
368
|
(Array.isArray(opts.includeCustomFields) && opts.includeCustomFields.length > 0)
|
|
360
369
|
)
|
|
361
370
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import { useRouter,
|
|
4
|
+
import { useRouter, usePathname } from 'next/navigation'
|
|
5
5
|
import { useQuery } from '@tanstack/react-query'
|
|
6
6
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
7
7
|
import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
|
|
@@ -33,20 +33,12 @@ import { MobileDefinitionDetail } from '../../../components/mobile/MobileDefinit
|
|
|
33
33
|
import { useIsMobile } from '@open-mercato/ui/hooks/useIsMobile'
|
|
34
34
|
import type { WorkflowDefinitionTrigger } from '../../../data/entities'
|
|
35
35
|
|
|
36
|
-
export default function EditWorkflowDefinitionPage() {
|
|
36
|
+
export default function EditWorkflowDefinitionPage({ params }: { params?: { id?: string } }) {
|
|
37
37
|
const router = useRouter()
|
|
38
|
-
const params = useParams()
|
|
39
38
|
const pathname = usePathname()
|
|
40
39
|
const t = useT()
|
|
41
40
|
const isMobile = useIsMobile()
|
|
42
|
-
|
|
43
|
-
// Handle catch-all route: params.slug = ['definitions', 'uuid']
|
|
44
|
-
let definitionId: string | undefined
|
|
45
|
-
if (params?.slug && Array.isArray(params.slug)) {
|
|
46
|
-
definitionId = params.slug[1] // Second element is the ID
|
|
47
|
-
} else if (params?.id) {
|
|
48
|
-
definitionId = Array.isArray(params.id) ? params.id[0] : params.id
|
|
49
|
-
}
|
|
41
|
+
const definitionId = params?.id
|
|
50
42
|
|
|
51
43
|
const { data: definition, isLoading, error } = useQuery({
|
|
52
44
|
queryKey: ['workflow-definition', definitionId],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import Link from 'next/link'
|
|
5
|
-
import {
|
|
5
|
+
import { useRouter } from 'next/navigation'
|
|
6
6
|
import { useQuery } from '@tanstack/react-query'
|
|
7
7
|
import { apiFetch } from '@open-mercato/ui/backend/utils/api'
|
|
8
8
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
@@ -36,19 +36,10 @@ type WorkflowEvent = {
|
|
|
36
36
|
} | null
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export default function WorkflowEventDetailPage() {
|
|
39
|
+
export default function WorkflowEventDetailPage({ params }: { params?: { id?: string } }) {
|
|
40
40
|
const router = useRouter()
|
|
41
|
-
const params = useParams()
|
|
42
41
|
const t = useT()
|
|
43
|
-
|
|
44
|
-
// Handle both {id: '17'} and {slug: ['events', '17']} formats
|
|
45
|
-
let eventId: string | undefined
|
|
46
|
-
if (params?.id) {
|
|
47
|
-
eventId = Array.isArray(params.id) ? params.id[0] : params.id
|
|
48
|
-
} else if (params?.slug && Array.isArray(params.slug)) {
|
|
49
|
-
// If slug is ['events', '17'], extract '17'
|
|
50
|
-
eventId = params.slug[params.slug.length - 1]
|
|
51
|
-
}
|
|
42
|
+
const eventId = params?.id
|
|
52
43
|
|
|
53
44
|
const { data: event, isLoading, error } = useQuery({
|
|
54
45
|
queryKey: ['workflows', 'events', eventId],
|