Skip to content

Commit 2dbc261

Browse files
committed
Add copy-from-version wand menu, build diagnostics, and checklist improvements
1 parent d68986a commit 2dbc261

21 files changed

Lines changed: 1445 additions & 111 deletions

File tree

docs/ASC-API.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,55 @@ Replace `{w}` and `{h}` with pixel dimensions, `{f}` with format (`png`, `jpg`,
314314
.../AppIcon.icns/64x64bb.png
315315
```
316316

317+
### Build metrics (betaBuildUsages)
318+
319+
```
320+
GET /v1/builds/{buildId}/metrics/betaBuildUsages
321+
```
322+
323+
Returns: `{ data: [{ dataPoints: [{ values: { ... } }] }] }`
324+
325+
Values fields:
326+
| Field | Type | Notes |
327+
|---|---|---|
328+
| `installCount` | number | Total installs |
329+
| `sessionCount` | number | Total sessions |
330+
| `crashCount` | number | Total crashes |
331+
| `inviteCount` | number | Total invites sent |
332+
| `feedbackCount` | number | Total feedback submissions |
333+
334+
### Build diagnostic signatures
335+
336+
```
337+
GET /v1/builds/{buildId}/diagnosticSignatures
338+
filter[diagnosticType] = DISK_WRITES | HANGS | LAUNCHES
339+
limit = 200
340+
```
341+
342+
Returns: `{ data: [{ id, attributes: { diagnosticType, signature, weight } }] }`
343+
344+
Signature attributes:
345+
| Field | Type | Notes |
346+
|---|---|---|
347+
| `diagnosticType` | string | `DISK_WRITES`, `HANGS`, or `LAUNCHES` |
348+
| `signature` | string | Human-readable signature (e.g. function name) |
349+
| `weight` | number | 0–1 fraction indicating relative frequency |
350+
351+
### Diagnostic logs
352+
353+
```
354+
GET /v1/diagnosticSignatures/{signatureId}/logs
355+
```
356+
357+
Returns: `{ data: [{ attributes: { diagnosticMetaData, callStackTree, insights } }] }`
358+
359+
- `diagnosticMetaData` – key-value pairs (deviceType, osVersion, etc.)
360+
- `callStackTree` – array of `{ callStacks: [{ callStackRootFrames: [frame, ...] }] }`
361+
- Each frame: `{ symbolName, binaryName, fileName?, lineNumber?, address?, isBlameFrame, sampleCount, subFrames? }`
362+
- `insights` – array of `{ category, description, url? }` with Apple's analysis
363+
364+
**Note:** Diagnostic data is available for all builds including expired ones. Signatures are cached for 15 minutes. Logs are fetched on-demand (no caching).
365+
317366
## Known API quirks
318367

319368
1. **`fields[type]` strips relationships** – the ASC API follows JSON:API sparse fieldsets: when you specify `fields[someType]=attr1,attr2`, the response omits **all** relationship pointers not listed. To keep relationship data needed by `include`, you must add the relationship names to `fields`. For example: `fields[appStoreVersions]=versionString,...,build,appStoreReviewDetail` – without `build,appStoreReviewDetail` in the list, the `relationships` key is missing from each version object and `resolveIncluded()` cannot match included items to their parents.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { NextResponse } from "next/server";
2+
import { errorJson } from "@/lib/api-helpers";
3+
import { getDiagnosticLogs } from "@/lib/asc/testflight";
4+
import { hasCredentials } from "@/lib/asc/client";
5+
6+
export async function GET(
7+
_request: Request,
8+
{ params }: { params: Promise<{ appId: string; buildId: string; signatureId: string }> },
9+
) {
10+
const { signatureId } = await params;
11+
12+
if (!hasCredentials()) {
13+
return NextResponse.json({ logs: [] });
14+
}
15+
16+
try {
17+
const logs = await getDiagnosticLogs(signatureId);
18+
return NextResponse.json({ logs });
19+
} catch (err) {
20+
return errorJson(err);
21+
}
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { NextResponse } from "next/server";
2+
import { errorJson } from "@/lib/api-helpers";
3+
import { listDiagnosticSignatures } from "@/lib/asc/testflight";
4+
import { hasCredentials } from "@/lib/asc/client";
5+
import type { TFDiagnosticType } from "@/lib/asc/testflight";
6+
7+
const VALID_TYPES = new Set(["DISK_WRITES", "HANGS", "LAUNCHES"]);
8+
9+
export async function GET(
10+
request: Request,
11+
{ params }: { params: Promise<{ appId: string; buildId: string }> },
12+
) {
13+
const { buildId } = await params;
14+
const url = new URL(request.url);
15+
const forceRefresh = url.searchParams.get("refresh") === "1";
16+
const typeParam = url.searchParams.get("type") ?? undefined;
17+
const type = typeParam && VALID_TYPES.has(typeParam)
18+
? (typeParam as TFDiagnosticType)
19+
: undefined;
20+
21+
if (!hasCredentials()) {
22+
return NextResponse.json({ signatures: [] });
23+
}
24+
25+
try {
26+
const signatures = await listDiagnosticSignatures(buildId, type, forceRefresh);
27+
return NextResponse.json({ signatures });
28+
} catch (err) {
29+
return errorJson(err);
30+
}
31+
}

src/app/dashboard/apps/[appId]/store-listing/page.tsx

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import {
2222
} from "@/lib/asc/locale-names";
2323
import { useRegisterHeaderLocale } from "@/lib/header-locale-context";
2424
import { useSubmissionChecklist } from "@/lib/submission-checklist-context";
25+
import { computeChecklistFlags } from "@/lib/submission-checklist-utils";
2526
import { useLocaleManagement } from "@/lib/hooks/use-locale-management";
26-
import type { MagicWandLocaleProps } from "@/components/magic-wand-button";
27+
import type { MagicWandLocaleProps, CopyFromVersion } from "@/components/magic-wand-button";
2728
import { BulkAIDialog, type BulkField } from "@/components/bulk-ai-dialog";
2829
import { BulkAllAIDialog } from "@/components/bulk-all-ai-dialog";
2930
import type { TFBuild } from "@/lib/asc/testflight/types";
@@ -99,11 +100,50 @@ export default function StoreListingPage() {
99100

100101
const current = localeData[selectedLocale] ?? emptyLocaleFields();
101102

103+
const copyFromVersions: CopyFromVersion[] = useMemo(
104+
() =>
105+
versions
106+
.filter((v) => v.id !== versionId)
107+
.map((v) => ({
108+
versionId: v.id,
109+
versionString: v.attributes.versionString,
110+
platform: v.attributes.platform,
111+
})),
112+
[versions, versionId],
113+
);
114+
115+
async function handleCopyFromVersion(field: string, sourceVersionId: string) {
116+
try {
117+
const res = await fetch(
118+
`/api/apps/${appId}/versions/${sourceVersionId}/localizations`,
119+
);
120+
if (!res.ok) {
121+
toast.error("Failed to fetch version localizations");
122+
return;
123+
}
124+
const data = await res.json();
125+
const locs: { attributes: { locale: string; [key: string]: string } }[] =
126+
data.localizations ?? [];
127+
const match = locs.find((l) => l.attributes.locale === selectedLocale);
128+
if (!match) {
129+
toast.error("Locale not available in that version");
130+
return;
131+
}
132+
const value = match.attributes[field] ?? "";
133+
updateField(field as keyof LocaleFields, value);
134+
toast.success("Copied from version");
135+
} catch {
136+
toast.error("Failed to fetch version localizations");
137+
}
138+
}
139+
102140
const wand: MagicWandLocaleProps = {
103141
locale: selectedLocale,
104142
baseLocale: locales[0] ?? "",
105143
localeData,
106144
appName: app?.name,
145+
copyFromVersions,
146+
onCopyFromVersion: handleCopyFromVersion,
107147
};
108148

109149
const { report: reportChecklist } = useSubmissionChecklist();
@@ -247,15 +287,10 @@ export default function StoreListingPage() {
247287
setValidationErrors(errors);
248288
}, [localeData, setValidationErrors]);
249289

250-
// Report submission checklist flags from primary locale
290+
// Report submission checklist flags across all locales
251291
useEffect(() => {
252-
const primary = localeData[primaryLocale];
253-
if (!primary) return;
254-
reportChecklist({
255-
hasDescription: (primary.description?.length ?? 0) > 0,
256-
hasWhatsNew: (primary.whatsNew?.length ?? 0) > 0,
257-
hasKeywords: (primary.keywords?.length ?? 0) > 0,
258-
});
292+
if (!localeData[primaryLocale]) return;
293+
reportChecklist(computeChecklistFlags(localeData, primaryLocale));
259294
}, [localeData, primaryLocale, reportChecklist]);
260295

261296
// Register save handler for the header Save button

0 commit comments

Comments
 (0)