Skip to content

Commit

Permalink
Enable search for dev stores
Browse files Browse the repository at this point in the history
  • Loading branch information
amcaplan committed Dec 24, 2024
1 parent 7aa290c commit 0feadff
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import * as Types from './types.js'

import {TypedDocumentNode as DocumentNode} from '@graphql-typed-document-node/core'

export type ListAppDevStoresQueryVariables = Types.Exact<{[key: string]: never}>
export type ListAppDevStoresQueryVariables = Types.Exact<{
searchTerm?: Types.InputMaybe<Types.Scalars['String']['input']>
}>

export type ListAppDevStoresQuery = {
organization?: {
Expand All @@ -20,6 +22,7 @@ export type ListAppDevStoresQuery = {
shortName?: string | null
}
}[]
pageInfo: {hasNextPage: boolean}
} | null
} | null
}
Expand All @@ -31,6 +34,13 @@ export const ListAppDevStores = {
kind: 'OperationDefinition',
operation: 'query',
name: {kind: 'Name', value: 'ListAppDevStores'},
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {kind: 'Variable', name: {kind: 'Name', value: 'searchTerm'}},
type: {kind: 'NamedType', name: {kind: 'Name', value: 'String'}},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
Expand Down Expand Up @@ -70,6 +80,11 @@ export const ListAppDevStores = {
],
},
},
{
kind: 'Argument',
name: {kind: 'Name', value: 'search'},
value: {kind: 'Variable', name: {kind: 'Name', value: 'searchTerm'}},
},
],
selectionSet: {
kind: 'SelectionSet',
Expand Down Expand Up @@ -100,6 +115,17 @@ export const ListAppDevStores = {
],
},
},
{
kind: 'Field',
name: {kind: 'Name', value: 'pageInfo'},
selectionSet: {
kind: 'SelectionSet',
selections: [
{kind: 'Field', name: {kind: 'Name', value: 'hasNextPage'}},
{kind: 'Field', name: {kind: 'Name', value: '__typename'}},
],
},
},
{kind: 'Field', name: {kind: 'Name', value: '__typename'}},
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
query ListAppDevStores {
organization {
id
name
accessibleShops(filters: {field: STORE_TYPE, operator: EQUALS, value: "app_development"}) {
edges {
node {
id
externalId
name
storeType
primaryDomain
shortName
}
query ListAppDevStores($searchTerm: String) {
organization {
id
name
accessibleShops(filters: {field: STORE_TYPE, operator: EQUALS, value: "app_development"}, search: $searchTerm) {
edges {
node {
id
externalId
name
storeType
primaryDomain
shortName
}
}
pageInfo {
hasNextPage
}
}
}
}
2 changes: 1 addition & 1 deletion packages/app/src/cli/models/app/app.test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1329,7 +1329,7 @@ export function testDeveloperPlatformClient(stubs: Partial<DeveloperPlatformClie
Promise.resolve({organization: testOrganization(), apps: [testOrganizationApp()], hasMorePages: false}),
createApp: (_organization: Organization, _name: string, _options?: CreateAppOptions) =>
Promise.resolve(testOrganizationApp()),
devStoresForOrg: (_organizationId: string) => Promise.resolve([]),
devStoresForOrg: (_organizationId: string) => Promise.resolve({stores: [], hasMorePages: false}),
storeByDomain: (_orgId: string, _shopDomain: string) => Promise.resolve({organizations: {nodes: []}}),
appExtensionRegistrations: (_app: MinimalAppIdentifiers) => Promise.resolve(emptyAppExtensionRegistrations),
appVersions: (_app: MinimalAppIdentifiers) => Promise.resolve(emptyAppVersions),
Expand Down
12 changes: 8 additions & 4 deletions packages/app/src/cli/prompts/dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ describe('selectStore', () => {
const stores: OrganizationStore[] = []

// When
const got = await selectStorePrompt(stores, defaultShowDomainOnPrompt)
const got = await selectStorePrompt({stores, showDomainOnPrompt: defaultShowDomainOnPrompt})

// Then
expect(got).toEqual(undefined)
Expand All @@ -154,7 +154,7 @@ describe('selectStore', () => {
const outputMock = mockAndCaptureOutput()

// When
const got = await selectStorePrompt(stores, defaultShowDomainOnPrompt)
const got = await selectStorePrompt({stores, showDomainOnPrompt: defaultShowDomainOnPrompt})

// Then
expect(got).toEqual(STORE1)
Expand All @@ -168,7 +168,7 @@ describe('selectStore', () => {
vi.mocked(renderAutocompletePrompt).mockResolvedValue('2')

// When
const got = await selectStorePrompt(stores, defaultShowDomainOnPrompt)
const got = await selectStorePrompt({stores, showDomainOnPrompt: defaultShowDomainOnPrompt})

// Then
expect(got).toEqual(STORE2)
Expand All @@ -178,6 +178,8 @@ describe('selectStore', () => {
{label: 'store1', value: '1'},
{label: 'store2', value: '2'},
],
hasMorePages: false,
search: expect.any(Function),
})
})

Expand All @@ -187,7 +189,7 @@ describe('selectStore', () => {
vi.mocked(renderAutocompletePrompt).mockResolvedValue('2')

// When
const got = await selectStorePrompt(stores, true)
const got = await selectStorePrompt({stores, showDomainOnPrompt: true})

// Then
expect(got).toEqual(STORE2)
Expand All @@ -197,6 +199,8 @@ describe('selectStore', () => {
{label: 'store1 (domain1)', value: '1'},
{label: 'store2 (domain2)', value: '2'},
],
hasMorePages: false,
search: expect.any(Function),
})
})
})
Expand Down
38 changes: 31 additions & 7 deletions packages/app/src/cli/prompts/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import {Organization, MinimalOrganizationApp, OrganizationStore, MinimalAppIdentifiers} from '../models/organization.js'
import {getTomls} from '../utilities/app/config/getTomls.js'
import {setCachedCommandTomlMap} from '../services/local-storage.js'
import {Paginateable} from '../utilities/developer-platform-client.js'
import {renderAutocompletePrompt, renderConfirmationPrompt, renderTextPrompt} from '@shopify/cli-kit/node/ui'
import {outputCompleted} from '@shopify/cli-kit/node/output'

Expand Down Expand Up @@ -58,27 +59,50 @@ export async function selectAppPrompt(
return currentAppChoices.find((app) => app.apiKey === apiKey)!
}

export async function selectStorePrompt(
stores: OrganizationStore[],
showDomainOnPrompt: boolean,
): Promise<OrganizationStore | undefined> {
interface SelectStorePromptOptions {
onSearchForStoresByName?: (term: string) => Promise<Paginateable<{stores: OrganizationStore[]}>>
stores: OrganizationStore[]
hasMorePages?: boolean
showDomainOnPrompt: boolean
}

export async function selectStorePrompt({
stores,
hasMorePages = false,
onSearchForStoresByName = (_term: string) => Promise.resolve({stores, hasMorePages}),
showDomainOnPrompt = true,
}: SelectStorePromptOptions): Promise<OrganizationStore | undefined> {
if (stores.length === 0) return undefined
if (stores.length === 1) {
outputCompleted(`Using your default dev store, ${stores[0]!.shopName}, to preview your project.`)
return stores[0]
}

const storeList = stores.map((store) => {
const toAnswer = (store: OrganizationStore) => {
let label = store.shopName
if (showDomainOnPrompt && store.shopDomain) {
label = `${store.shopName} (${store.shopDomain})`
}
return {label, value: store.shopId}
})
}

let currentStoreChoices = stores

const id = await renderAutocompletePrompt({
message: 'Which store would you like to use to view your project?',
choices: storeList,
choices: currentStoreChoices.map(toAnswer),
hasMorePages,
search: async (term) => {
const result = await onSearchForStoresByName(term)
currentStoreChoices = result.stores

return {
data: currentStoreChoices.map(toAnswer),
meta: {
hasNextPage: result.hasMorePages,
},
}
},
})
return stores.find((store) => store.shopId === id)
}
Expand Down
62 changes: 48 additions & 14 deletions packages/app/src/cli/services/dev/select-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,16 @@ describe('selectStore', async () => {
vi.mocked(selectStorePrompt).mockResolvedValueOnce(STORE1)

// When
const got = await selectStore([STORE1, STORE2], ORG1, testDeveloperPlatformClient())
const got = await selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, testDeveloperPlatformClient())

// Then
expect(got).toEqual(STORE1)
expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], defaultShowDomainOnPrompt)
expect(selectStorePrompt).toHaveBeenCalledWith(
expect.objectContaining({
stores: [STORE1, STORE2],
showDomainOnPrompt: defaultShowDomainOnPrompt,
}),
)
})

test('selectStorePrompt is called with showDomainOnPrompt = true if clientName is app-management', async () => {
Expand All @@ -73,11 +78,16 @@ describe('selectStore', async () => {
const developerPlatformClient = testDeveloperPlatformClient({clientName: ClientName.AppManagement})

// When
const got = await selectStore([STORE1, STORE2], ORG1, developerPlatformClient)
const got = await selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, developerPlatformClient)

// Then
expect(got).toEqual(STORE1)
expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], true)
expect(selectStorePrompt).toHaveBeenCalledWith(
expect.objectContaining({
stores: [STORE1, STORE2],
showDomainOnPrompt: true,
}),
)
})

test('prompts user to convert store to non-transferable if selection is invalid', async () => {
Expand All @@ -86,11 +96,16 @@ describe('selectStore', async () => {
vi.mocked(confirmConversionToTransferDisabledStorePrompt).mockResolvedValueOnce(true)

// When
const got = await selectStore([STORE1, STORE2], ORG1, testDeveloperPlatformClient())
const got = await selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, testDeveloperPlatformClient())

// Then
expect(got).toEqual(STORE2)
expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], defaultShowDomainOnPrompt)
expect(selectStorePrompt).toHaveBeenCalledWith(
expect.objectContaining({
stores: [STORE1, STORE2],
showDomainOnPrompt: defaultShowDomainOnPrompt,
}),
)
expect(confirmConversionToTransferDisabledStorePrompt).toHaveBeenCalled()
})

Expand All @@ -101,11 +116,16 @@ describe('selectStore', async () => {
vi.mocked(confirmConversionToTransferDisabledStorePrompt).mockResolvedValueOnce(false)

// When
const got = await selectStore([STORE1, STORE2], ORG1, testDeveloperPlatformClient())
const got = await selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, testDeveloperPlatformClient())

// Then
expect(got).toEqual(STORE1)
expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], defaultShowDomainOnPrompt)
expect(selectStorePrompt).toHaveBeenCalledWith(
expect.objectContaining({
stores: [STORE1, STORE2],
showDomainOnPrompt: defaultShowDomainOnPrompt,
}),
)
expect(confirmConversionToTransferDisabledStorePrompt).toHaveBeenCalled()
})

Expand All @@ -117,20 +137,29 @@ describe('selectStore', async () => {
const developerPlatformClient = testDeveloperPlatformClient()

// When
const got = await selectStore([STORE1, STORE2], ORG1, developerPlatformClient)
const got = await selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, developerPlatformClient)

// Then
expect(got).toEqual(STORE2)
expect(developerPlatformClient.convertToTransferDisabledStore).not.toHaveBeenCalled()
expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], defaultShowDomainOnPrompt)
expect(selectStorePrompt).toHaveBeenCalledWith(
expect.objectContaining({
stores: [STORE1, STORE2],
showDomainOnPrompt: defaultShowDomainOnPrompt,
}),
)
})

test('throws if store is non convertible', async () => {
// Given
vi.mocked(selectStorePrompt).mockResolvedValueOnce(STORE3)

// When
const got = selectStore([STORE1, STORE2, STORE3], ORG1, testDeveloperPlatformClient())
const got = selectStore(
{stores: [STORE1, STORE2, STORE3], hasMorePages: false},
ORG1,
testDeveloperPlatformClient(),
)

// Then
await expect(got).rejects.toThrow('The store you specified (domain3) is not a dev store')
Expand All @@ -142,11 +171,16 @@ describe('selectStore', async () => {
vi.mocked(reloadStoreListPrompt).mockResolvedValue(false)

// When
const got = () => selectStore([STORE1, STORE2], ORG1, testDeveloperPlatformClient())
const got = () => selectStore({stores: [STORE1, STORE2], hasMorePages: false}, ORG1, testDeveloperPlatformClient())

// Then
await expect(got).rejects.toThrowError()
expect(selectStorePrompt).toHaveBeenCalledWith([STORE1, STORE2], defaultShowDomainOnPrompt)
expect(selectStorePrompt).toHaveBeenCalledWith(
expect.objectContaining({
stores: [STORE1, STORE2],
showDomainOnPrompt: defaultShowDomainOnPrompt,
}),
)
})

test('prompts user to create & reload, fetches 10 times and tries again if reload is true', async () => {
Expand All @@ -157,7 +191,7 @@ describe('selectStore', async () => {
const developerPlatformClient = testDeveloperPlatformClient()

// When
const got = selectStore([], ORG1, developerPlatformClient)
const got = selectStore({stores: [], hasMorePages: false}, ORG1, developerPlatformClient)

// Then
await expect(got).rejects.toThrow()
Expand Down
Loading

0 comments on commit 0feadff

Please sign in to comment.