Skip to content

Commit ddbf27d

Browse files
authored
Merge pull request #5938 from Shopify/reorder-extension-templates
Add sort order to generate extensions list
2 parents 4ef95e9 + 17bd727 commit ddbf27d

File tree

15 files changed

+256
-89
lines changed

15 files changed

+256
-89
lines changed

packages/app/src/cli/models/app/app.test-data.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1431,7 +1431,8 @@ export function testDeveloperPlatformClient(stubs: Partial<DeveloperPlatformClie
14311431
orgFromId: (_organizationId: string) => Promise.resolve(testOrganization()),
14321432
appsForOrg: (_organizationId: string) => Promise.resolve({apps: [testOrganizationApp()], hasMorePages: false}),
14331433
specifications: (_app: MinimalAppIdentifiers) => Promise.resolve(testRemoteSpecifications),
1434-
templateSpecifications: (_app: MinimalAppIdentifiers) => Promise.resolve(testRemoteExtensionTemplates),
1434+
templateSpecifications: (_app: MinimalAppIdentifiers) =>
1435+
Promise.resolve({templates: testRemoteExtensionTemplates, groupOrder: []}),
14351436
orgAndApps: (_orgId: string) =>
14361437
Promise.resolve({organization: testOrganization(), apps: [testOrganizationApp()], hasMorePages: false}),
14371438
createApp: (_organization: Organization, _options: CreateAppOptions) => Promise.resolve(testOrganizationApp()),

packages/app/src/cli/models/app/template.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@ export interface ExtensionTemplate {
1818
supportedFlavors: ExtensionFlavor[]
1919
url: string
2020
}
21+
22+
export interface ExtensionTemplatesResult {
23+
templates: ExtensionTemplate[]
24+
groupOrder: string[]
25+
}

packages/app/src/cli/prompts/generate/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface GenerateExtensionPromptOptions {
1616
extensionTemplates: ExtensionTemplate[]
1717
unavailableExtensions: ExtensionTemplate[]
1818
reset: boolean
19+
groupOrder?: string[]
1920
}
2021

2122
export interface GenerateExtensionPromptOutput {
@@ -85,6 +86,7 @@ const generateExtensionPrompts = async (
8586
templateType = await renderAutocompletePrompt({
8687
message: 'Type of extension?',
8788
choices: buildChoices(extensionTemplates, options.unavailableExtensions),
89+
groupOrder: options.groupOrder,
8890
})
8991
}
9092

packages/app/src/cli/services/generate.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,13 @@ async function generate(options: GenerateOptions) {
4040
const {app, developerPlatformClient, remoteApp, specifications, template} = options
4141

4242
const availableSpecifications = specifications.map((spec) => spec.identifier)
43-
const extensionTemplates = await fetchExtensionTemplates(developerPlatformClient, remoteApp, availableSpecifications)
43+
const {templates: extensionTemplates, groupOrder} = await fetchExtensionTemplates(
44+
developerPlatformClient,
45+
remoteApp,
46+
availableSpecifications,
47+
)
4448

45-
const promptOptions = await buildPromptOptions(extensionTemplates, specifications, app, options)
49+
const promptOptions = await buildPromptOptions(extensionTemplates, groupOrder, specifications, app, options)
4650
const promptAnswers = await generateExtensionPrompts(promptOptions)
4751

4852
await saveAnalyticsMetadata(promptAnswers, template)
@@ -55,6 +59,7 @@ async function generate(options: GenerateOptions) {
5559

5660
async function buildPromptOptions(
5761
extensionTemplates: ExtensionTemplate[],
62+
groupOrder: string[],
5863
specifications: ExtensionSpecification[],
5964
app: AppInterface,
6065
options: GenerateOptions,
@@ -73,6 +78,7 @@ async function buildPromptOptions(
7378
extensionTemplates: validTemplates ?? [],
7479
unavailableExtensions: templatesOverlimit ?? [],
7580
reset: options.reset,
81+
groupOrder,
7682
}
7783
}
7884

packages/app/src/cli/services/generate/extension.test.ts

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -473,32 +473,35 @@ describe('initialize a extension', async () => {
473473
specifications,
474474
developerPlatformClient: testDeveloperPlatformClient({
475475
templateSpecifications: () =>
476-
Promise.resolve([
477-
{
478-
identifier: 'ui_extension',
479-
name: 'UI Extension',
480-
defaultName: 'ui-extension',
481-
group: 'Merchant Admin',
482-
supportLinks: [],
483-
type: 'ui_extension',
484-
url: 'https://github.com/Shopify/extensions-templates',
485-
extensionPoints: [],
486-
supportedFlavors: [
487-
{
488-
name: 'JavaScript',
489-
value: 'vanilla-js',
490-
},
491-
{
492-
name: 'TypeScript',
493-
value: 'typescript',
494-
},
495-
{
496-
name: 'React',
497-
value: 'react',
498-
},
499-
],
500-
},
501-
]),
476+
Promise.resolve({
477+
templates: [
478+
{
479+
identifier: 'ui_extension',
480+
name: 'UI Extension',
481+
defaultName: 'ui-extension',
482+
group: 'Merchant Admin',
483+
supportLinks: [],
484+
type: 'ui_extension',
485+
url: 'https://github.com/Shopify/extensions-templates',
486+
extensionPoints: [],
487+
supportedFlavors: [
488+
{
489+
name: 'JavaScript',
490+
value: 'vanilla-js',
491+
},
492+
{
493+
name: 'TypeScript',
494+
value: 'typescript',
495+
},
496+
{
497+
name: 'React',
498+
value: 'react',
499+
},
500+
],
501+
},
502+
],
503+
groupOrder: [],
504+
}),
502505
}),
503506
})
504507

packages/app/src/cli/services/generate/fetch-template-specifications.test.ts

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {fetchExtensionTemplates} from './fetch-template-specifications.js'
22
import {ExtensionFlavorValue} from './extension.js'
33
import {testDeveloperPlatformClient, testOrganizationApp} from '../../models/app/app.test-data.js'
4-
import {ExtensionTemplate, ExtensionFlavor} from '../../models/app/template.js'
4+
import {ExtensionFlavor} from '../../models/app/template.js'
55
import {describe, expect, test, vi} from 'vitest'
66
import * as experimentModule from '@shopify/cli-kit/node/is-polaris-unified-enabled'
77

@@ -15,7 +15,7 @@ describe('fetchTemplateSpecifications', () => {
1515
const enabledSpecifications = ['function']
1616

1717
// When
18-
const got: ExtensionTemplate[] = await fetchExtensionTemplates(
18+
const {templates: got} = await fetchExtensionTemplates(
1919
testDeveloperPlatformClient(),
2020
testOrganizationApp(),
2121
enabledSpecifications,
@@ -64,22 +64,25 @@ describe('fetchTemplateSpecifications', () => {
6464
const allFlavors = [...oldFlavors, preactFlavor]
6565

6666
async function getTemplates() {
67-
const templates: ExtensionTemplate[] = await fetchExtensionTemplates(
67+
const {templates} = await fetchExtensionTemplates(
6868
testDeveloperPlatformClient({
6969
templateSpecifications: () =>
70-
Promise.resolve([
71-
{
72-
identifier: 'ui_extension',
73-
name: 'UI Extension',
74-
defaultName: 'ui-extension',
75-
group: 'Merchant Admin',
76-
supportLinks: [],
77-
type: 'ui_extension',
78-
url: 'https://github.com/Shopify/extensions-templates',
79-
extensionPoints: [],
80-
supportedFlavors: allFlavors,
81-
},
82-
]),
70+
Promise.resolve({
71+
templates: [
72+
{
73+
identifier: 'ui_extension',
74+
name: 'UI Extension',
75+
defaultName: 'ui-extension',
76+
group: 'Merchant Admin',
77+
supportLinks: [],
78+
type: 'ui_extension',
79+
url: 'https://github.com/Shopify/extensions-templates',
80+
extensionPoints: [],
81+
supportedFlavors: allFlavors,
82+
},
83+
],
84+
groupOrder: [],
85+
}),
8386
}),
8487
testOrganizationApp(),
8588
['ui_extension'],
@@ -108,22 +111,25 @@ describe('fetchTemplateSpecifications', () => {
108111

109112
test('filter out templates that have no flavors available by default', async () => {
110113
// When
111-
const templates: ExtensionTemplate[] = await fetchExtensionTemplates(
114+
const {templates} = await fetchExtensionTemplates(
112115
testDeveloperPlatformClient({
113116
templateSpecifications: () =>
114-
Promise.resolve([
115-
{
116-
identifier: 'ui_extension',
117-
name: 'UI Extension',
118-
defaultName: 'ui-extension',
119-
group: 'Merchant Admin',
120-
supportLinks: [],
121-
type: 'ui_extension',
122-
url: 'https://github.com/Shopify/extensions-templates',
123-
extensionPoints: [],
124-
supportedFlavors: [preactFlavor],
125-
},
126-
]),
117+
Promise.resolve({
118+
templates: [
119+
{
120+
identifier: 'ui_extension',
121+
name: 'UI Extension',
122+
defaultName: 'ui-extension',
123+
group: 'Merchant Admin',
124+
supportLinks: [],
125+
type: 'ui_extension',
126+
url: 'https://github.com/Shopify/extensions-templates',
127+
extensionPoints: [],
128+
supportedFlavors: [preactFlavor],
129+
},
130+
],
131+
groupOrder: [],
132+
}),
127133
}),
128134
testOrganizationApp(),
129135
['ui_extension'],
@@ -138,22 +144,25 @@ describe('fetchTemplateSpecifications', () => {
138144
vi.spyOn(experimentModule, 'isPolarisUnifiedEnabled').mockReturnValueOnce(true)
139145

140146
// When
141-
const templates: ExtensionTemplate[] = await fetchExtensionTemplates(
147+
const {templates} = await fetchExtensionTemplates(
142148
testDeveloperPlatformClient({
143149
templateSpecifications: () =>
144-
Promise.resolve([
145-
{
146-
identifier: 'ui_extension',
147-
name: 'UI Extension',
148-
defaultName: 'ui-extension',
149-
group: 'Merchant Admin',
150-
supportLinks: [],
151-
type: 'ui_extension',
152-
url: 'https://github.com/Shopify/extensions-templates',
153-
extensionPoints: [],
154-
supportedFlavors: oldFlavors,
155-
},
156-
]),
150+
Promise.resolve({
151+
templates: [
152+
{
153+
identifier: 'ui_extension',
154+
name: 'UI Extension',
155+
defaultName: 'ui-extension',
156+
group: 'Merchant Admin',
157+
supportLinks: [],
158+
type: 'ui_extension',
159+
url: 'https://github.com/Shopify/extensions-templates',
160+
extensionPoints: [],
161+
supportedFlavors: oldFlavors,
162+
},
163+
],
164+
groupOrder: [],
165+
}),
157166
}),
158167
testOrganizationApp(),
159168
['ui_extension'],

packages/app/src/cli/services/generate/fetch-template-specifications.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {ExtensionTemplate} from '../../models/app/template.js'
1+
import {ExtensionTemplatesResult} from '../../models/app/template.js'
22
import {MinimalAppIdentifiers} from '../../models/organization.js'
33
import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
44
import {isPolarisUnifiedEnabled} from '@shopify/cli-kit/node/is-polaris-unified-enabled'
@@ -7,11 +7,11 @@ export async function fetchExtensionTemplates(
77
developerPlatformClient: DeveloperPlatformClient,
88
app: MinimalAppIdentifiers,
99
availableSpecifications: string[],
10-
): Promise<ExtensionTemplate[]> {
11-
const remoteTemplates: ExtensionTemplate[] = await developerPlatformClient.templateSpecifications(app)
10+
): Promise<ExtensionTemplatesResult> {
11+
const {templates: remoteTemplates, groupOrder} = await developerPlatformClient.templateSpecifications(app)
1212
const polarisUnifiedEnabled = isPolarisUnifiedEnabled()
1313

14-
return remoteTemplates
14+
const filteredTemplates = remoteTemplates
1515
.filter(
1616
(template) =>
1717
availableSpecifications.includes(template.identifier) || availableSpecifications.includes(template.type),
@@ -28,4 +28,9 @@ export async function fetchExtensionTemplates(
2828
return template
2929
})
3030
.filter((template) => template.supportedFlavors.length > 0)
31+
32+
return {
33+
templates: filteredTemplates,
34+
groupOrder,
35+
}
3136
}

packages/app/src/cli/utilities/developer-platform-client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
} from '../api/graphql/extension_migrate_flow_extension.js'
3636
import {UpdateURLsSchema, UpdateURLsVariables} from '../api/graphql/update_urls.js'
3737
import {CurrentAccountInfoSchema} from '../api/graphql/current_account_info.js'
38-
import {ExtensionTemplate} from '../models/app/template.js'
38+
import {ExtensionTemplatesResult} from '../models/app/template.js'
3939
import {SchemaDefinitionByTargetQueryVariables} from '../api/graphql/functions/generated/schema-definition-by-target.js'
4040
import {SchemaDefinitionByApiTypeQueryVariables} from '../api/graphql/functions/generated/schema-definition-by-api-type.js'
4141
import {
@@ -275,7 +275,7 @@ export interface DeveloperPlatformClient {
275275
orgAndApps: (orgId: string) => Promise<Paginateable<{organization: Organization; apps: MinimalOrganizationApp[]}>>
276276
appsForOrg: (orgId: string, term?: string) => Promise<Paginateable<{apps: MinimalOrganizationApp[]}>>
277277
specifications: (app: MinimalAppIdentifiers) => Promise<RemoteSpecification[]>
278-
templateSpecifications: (app: MinimalAppIdentifiers) => Promise<ExtensionTemplate[]>
278+
templateSpecifications: (app: MinimalAppIdentifiers) => Promise<ExtensionTemplatesResult>
279279
createApp: (org: Organization, options: CreateAppOptions) => Promise<OrganizationApp>
280280
devStoresForOrg: (orgId: string, searchTerm?: string) => Promise<Paginateable<{stores: OrganizationStore[]}>>
281281
storeByDomain: (orgId: string, shopDomain: string) => Promise<OrganizationStore | undefined>

packages/app/src/cli/utilities/developer-platform-client/app-management-client.test.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ describe('templateSpecifications', () => {
157157
// When
158158
const client = new AppManagementClient()
159159
client.businessPlatformToken = () => Promise.resolve('business-platform-token')
160-
const got = await client.templateSpecifications(orgApp)
160+
const {templates: got} = await client.templateSpecifications(orgApp)
161161
const gotLabels = got.map((template) => template.name)
162162
const gotSortPriorities = got.map((template) => template.sortPriority)
163163

@@ -185,7 +185,7 @@ describe('templateSpecifications', () => {
185185
// When
186186
const client = new AppManagementClient()
187187
client.businessPlatformToken = () => Promise.resolve('business-platform-token')
188-
const got = await client.templateSpecifications(orgApp)
188+
const {templates: got} = await client.templateSpecifications(orgApp)
189189
const gotLabels = got.map((template) => template.name)
190190

191191
// Then
@@ -210,6 +210,35 @@ describe('templateSpecifications', () => {
210210
expect(gotLabels).toEqual(expectedAllowedTemplates.map((template) => template.name))
211211
})
212212

213+
test('extracts groupOrder correctly from template order', async () => {
214+
// Given
215+
const orgApp = testOrganizationApp()
216+
const templates: GatedExtensionTemplate[] = [
217+
{...templateWithoutRules, group: 'GroupA'},
218+
{...allowedTemplate, group: 'GroupB'},
219+
// Same group as first
220+
{...templateWithoutRules, identifier: 'template3', group: 'GroupA'},
221+
{...allowedTemplate, identifier: 'template4', group: 'GroupC'},
222+
]
223+
const mockedFetch = vi.fn().mockResolvedValueOnce(Response.json(templates))
224+
vi.mocked(fetch).mockImplementation(mockedFetch)
225+
const mockedFetchFlagsResponse: OrganizationBetaFlagsQuerySchema = {
226+
organization: {
227+
id: encodedGidFromOrganizationId(orgApp.organizationId),
228+
flag_allowedFlag: true,
229+
},
230+
}
231+
vi.mocked(businessPlatformOrganizationsRequest).mockResolvedValueOnce(mockedFetchFlagsResponse)
232+
233+
// When
234+
const client = new AppManagementClient()
235+
client.businessPlatformToken = () => Promise.resolve('business-platform-token')
236+
const {groupOrder} = await client.templateSpecifications(orgApp)
237+
238+
// Then
239+
expect(groupOrder).toEqual(['GroupA', 'GroupB', 'GroupC'])
240+
})
241+
213242
test('fails with an error message when fetching the specifications list fails', async () => {
214243
// Given
215244
vi.mocked(fetch).mockRejectedValueOnce(new Error('Failed to fetch'))

0 commit comments

Comments
 (0)