Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions internal/asc/assets_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,12 @@ type AppScreenshotFanoutUploadResult struct {

// AppPreviewUploadResult represents preview upload output.
type AppPreviewUploadResult struct {
VersionLocalizationID string `json:"versionLocalizationId"`
SetID string `json:"setId"`
PreviewType string `json:"previewType"`
DryRun bool `json:"dryRun,omitempty"`
Results []AssetUploadResultItem `json:"results"`
VersionLocalizationID string `json:"versionLocalizationId"`
SetID string `json:"setId"`
PreviewType string `json:"previewType"`
DryRun bool `json:"dryRun,omitempty"`
Results []AssetUploadResultItem `json:"results"`
Failures []AssetUploadFailureItem `json:"failures,omitempty"`
}

// CustomProductPageScreenshotUploadResult represents custom product page screenshot upload output.
Expand Down
4 changes: 4 additions & 0 deletions internal/asc/output_registry_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ func registerAllOutputRenderers() {
ih, ir := assetUploadResultItemRows(v.Results)
render(ih, ir)
}
if len(v.Failures) > 0 {
fh, fr := assetUploadFailureItemRows(v.Failures)
render(fh, fr)
}
return nil
})
registerDirect(func(v *CustomProductPageScreenshotUploadResult, render func([]string, [][]string)) error {
Expand Down
15 changes: 15 additions & 0 deletions internal/asc/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,21 @@ func TestPrintTable_SkippedAssetUploadResultShowsSkippedState(t *testing.T) {
}
}

func TestPrintTable_AppPreviewUploadResultRendersPartialFailures(t *testing.T) {
assertRenderedNonJSONContains(t, PrintTable, &AppPreviewUploadResult{
VersionLocalizationID: "LOC_123",
SetID: "SET_123",
PreviewType: "IPHONE_65",
Results: []AssetUploadResultItem{
{FileName: "01-first.mov", AssetID: "PREVIEW_1", State: "COMPLETE"},
{FileName: "02-second.mov", FilePath: "/tmp/02-second.mov", State: "failed"},
},
Failures: []AssetUploadFailureItem{
{FileName: "02-second.mov", FilePath: "/tmp/02-second.mov", Error: "preview reservation failed"},
},
}, "SET_123", "IPHONE_65", "01-first.mov", "PREVIEW_1", "failed", "preview reservation failed")
}

func TestPrintTableAndMarkdown_AppScreenshotFanoutUploadResultIncludesFlattenedFileRows(t *testing.T) {
resp := &AppScreenshotFanoutUploadResult{
AppID: "123456789",
Expand Down
156 changes: 156 additions & 0 deletions internal/cli/assets/assets_order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package assets

import (
"context"
"fmt"
"strings"

"github.com/rudrankriyam/App-Store-Connect-CLI/internal/asc"
)

// collectOrderedLinkageIDs walks every linkage page and returns the linked
// resource IDs in the order App Store Connect reports them.
func collectOrderedLinkageIDs(ctx context.Context, firstPage *asc.LinkagesResponse, next func(context.Context, string) (asc.PaginatedResponse, error)) ([]string, error) {
if firstPage == nil {
return nil, fmt.Errorf("linkage response is required")
}

orderedIDs := make([]string, 0, len(firstPage.Data))
err := asc.PaginateEach(ctx, firstPage, next, func(page asc.PaginatedResponse) error {
linkages, ok := page.(*asc.LinkagesResponse)
if !ok {
return fmt.Errorf("unexpected relationship response type %T", page)
}
for _, item := range linkages.Data {
orderedIDs = appendUniqueAssetID(orderedIDs, item.ID)
}
return nil
})
if err != nil {
return nil, err
}

return orderedIDs, nil
}

// orderAssetIDsForLocalFiles orders asset IDs by the local file order of the
// current run and appends any remaining remote IDs in their existing order.
func orderAssetIDsForLocalFiles(currentOrder []string, files []string, skippedResults, uploadedResults []asc.AssetUploadResultItem) []string {
skippedByPath := make(map[string]string, len(skippedResults))
for _, item := range skippedResults {
if strings.TrimSpace(item.AssetID) == "" {
continue
}
skippedByPath[item.FilePath] = item.AssetID
}
uploadedByPath := make(map[string]string, len(uploadedResults))
for _, item := range uploadedResults {
if strings.TrimSpace(item.AssetID) == "" {
continue
}
uploadedByPath[item.FilePath] = item.AssetID
}

orderedIDs := make([]string, 0, len(currentOrder)+len(uploadedResults))
seen := make(map[string]struct{}, len(currentOrder)+len(uploadedResults))
for _, filePath := range files {
id := skippedByPath[filePath]
if id == "" {
id = uploadedByPath[filePath]
}
id = strings.TrimSpace(id)
if id == "" {
continue
}
if _, exists := seen[id]; exists {
continue
}
seen[id] = struct{}{}
orderedIDs = append(orderedIDs, id)
}
for _, id := range currentOrder {
id = strings.TrimSpace(id)
if id == "" {
continue
}
if _, exists := seen[id]; exists {
continue
}
seen[id] = struct{}{}
orderedIDs = append(orderedIDs, id)
}

return orderedIDs
}

// appendUploadedAssetIDs keeps the remote order of assets that already existed
// before this run and appends the newly uploaded assets in upload order.
func appendUploadedAssetIDs(currentOrder []string, uploadedResults []asc.AssetUploadResultItem) []string {
uploadedIDs := make(map[string]struct{}, len(uploadedResults))
for _, item := range uploadedResults {
if id := strings.TrimSpace(item.AssetID); id != "" {
uploadedIDs[id] = struct{}{}
}
}

orderedIDs := make([]string, 0, len(currentOrder)+len(uploadedResults))
for _, id := range currentOrder {
if _, uploaded := uploadedIDs[strings.TrimSpace(id)]; uploaded {
continue
}
orderedIDs = appendUniqueAssetID(orderedIDs, id)
}
for _, item := range uploadedResults {
orderedIDs = appendUniqueAssetID(orderedIDs, item.AssetID)
}

return orderedIDs
}

func sameAssetIDOrder(a, b []string) bool {
a = normalizeAssetIDs(a)
b = normalizeAssetIDs(b)
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}

func normalizeAssetIDs(ids []string) []string {
if len(ids) == 0 {
return nil
}

seen := make(map[string]struct{}, len(ids))
normalized := make([]string, 0, len(ids))
for _, id := range ids {
id = strings.TrimSpace(id)
if id == "" {
continue
}
if _, exists := seen[id]; exists {
continue
}
seen[id] = struct{}{}
normalized = append(normalized, id)
}
return normalized
}

func appendUniqueAssetID(ids []string, id string) []string {
id = strings.TrimSpace(id)
if id == "" {
return ids
}
for _, existing := range ids {
if existing == id {
return ids
}
}
return append(ids, id)
}
Loading