Skip to content

Commit 678ae08

Browse files
committed
Only show active stores for app dev
1 parent 5837ad2 commit 678ae08

4 files changed

Lines changed: 171 additions & 15 deletions

File tree

packages/app/src/cli/api/graphql/business-platform-organizations/generated/list_app_dev_stores.ts

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,22 +62,47 @@ export const ListAppDevStores = {
6262
kind: 'Argument',
6363
name: {kind: 'Name', value: 'filters'},
6464
value: {
65-
kind: 'ObjectValue',
66-
fields: [
65+
kind: 'ListValue',
66+
values: [
6767
{
68-
kind: 'ObjectField',
69-
name: {kind: 'Name', value: 'field'},
70-
value: {kind: 'EnumValue', value: 'STORE_TYPE'},
71-
},
72-
{
73-
kind: 'ObjectField',
74-
name: {kind: 'Name', value: 'operator'},
75-
value: {kind: 'EnumValue', value: 'EQUALS'},
68+
kind: 'ObjectValue',
69+
fields: [
70+
{
71+
kind: 'ObjectField',
72+
name: {kind: 'Name', value: 'field'},
73+
value: {kind: 'EnumValue', value: 'STORE_TYPE'},
74+
},
75+
{
76+
kind: 'ObjectField',
77+
name: {kind: 'Name', value: 'operator'},
78+
value: {kind: 'EnumValue', value: 'EQUALS'},
79+
},
80+
{
81+
kind: 'ObjectField',
82+
name: {kind: 'Name', value: 'value'},
83+
value: {kind: 'StringValue', value: 'app_development', block: false},
84+
},
85+
],
7686
},
7787
{
78-
kind: 'ObjectField',
79-
name: {kind: 'Name', value: 'value'},
80-
value: {kind: 'StringValue', value: 'app_development', block: false},
88+
kind: 'ObjectValue',
89+
fields: [
90+
{
91+
kind: 'ObjectField',
92+
name: {kind: 'Name', value: 'field'},
93+
value: {kind: 'EnumValue', value: 'STORE_STATUS'},
94+
},
95+
{
96+
kind: 'ObjectField',
97+
name: {kind: 'Name', value: 'operator'},
98+
value: {kind: 'EnumValue', value: 'EQUALS'},
99+
},
100+
{
101+
kind: 'ObjectField',
102+
name: {kind: 'Name', value: 'value'},
103+
value: {kind: 'StringValue', value: 'ACTIVE', block: false},
104+
},
105+
],
81106
},
82107
],
83108
},

packages/app/src/cli/api/graphql/business-platform-organizations/queries/list_app_dev_stores.graphql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ query ListAppDevStores($searchTerm: String) {
22
organization {
33
id
44
name
5-
accessibleShops(filters: {field: STORE_TYPE, operator: EQUALS, value: "app_development"}, search: $searchTerm) {
5+
accessibleShops(
6+
filters: [
7+
{field: STORE_TYPE, operator: EQUALS, value: "app_development"}
8+
{field: STORE_STATUS, operator: EQUALS, value: "ACTIVE"}
9+
]
10+
search: $searchTerm
11+
) {
612
edges {
713
node {
814
id

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

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import {
1919
} from '../../models/app/app.test-data.js'
2020
import {ExtensionInstance} from '../../models/extensions/extension-instance.js'
2121
import {ListApps} from '../../api/graphql/app-management/generated/apps.js'
22+
import {
23+
ListAppDevStores,
24+
ListAppDevStoresQuery,
25+
} from '../../api/graphql/business-platform-organizations/generated/list_app_dev_stores.js'
2226
import {PublicApiVersionsQuery} from '../../api/graphql/webhooks/generated/public-api-versions.js'
2327
import {AvailableTopicsQuery} from '../../api/graphql/webhooks/generated/available-topics.js'
2428
import {CliTesting, CliTestingMutation} from '../../api/graphql/webhooks/generated/cli-testing.js'
@@ -2052,3 +2056,117 @@ describe('uidStrategyFromTypename', () => {
20522056
expect(uidStrategyFromTypename('UnknownStrategy')).toBe('uuid')
20532057
})
20542058
})
2059+
2060+
describe('devStoresForOrg', () => {
2061+
test('queries Business Platform with both STORE_TYPE and STORE_STATUS=ACTIVE filters', async () => {
2062+
// Given
2063+
const orgGid = 'gid://shopify/Organization/123'
2064+
const searchTerm = 'my-store'
2065+
const mockedResponse: ListAppDevStoresQuery = {
2066+
organization: {
2067+
id: orgGid,
2068+
name: 'Org 123',
2069+
accessibleShops: {
2070+
edges: [
2071+
{
2072+
node: {
2073+
id: 'gid://BusinessPlatform/Shop/1',
2074+
externalId: encodedGidFromShopId('1'),
2075+
name: 'My Active Store',
2076+
storeType: 'APP_DEVELOPMENT',
2077+
primaryDomain: 'my-active-store.myshopify.com',
2078+
shortName: 'my-active-store',
2079+
url: 'https://my-active-store.myshopify.com',
2080+
},
2081+
},
2082+
],
2083+
pageInfo: {hasNextPage: false},
2084+
},
2085+
currentUser: {organizationPermissions: ['ondemand_access_to_stores']},
2086+
},
2087+
}
2088+
vi.mocked(businessPlatformOrganizationsRequestDoc).mockResolvedValueOnce(mockedResponse)
2089+
2090+
// When
2091+
const client = AppManagementClient.getInstance()
2092+
client.businessPlatformToken = () => Promise.resolve('business-platform-token')
2093+
const result = await client.devStoresForOrg(orgGid, searchTerm)
2094+
2095+
// Then
2096+
expect(vi.mocked(businessPlatformOrganizationsRequestDoc)).toHaveBeenCalledWith({
2097+
query: ListAppDevStores,
2098+
token: 'business-platform-token',
2099+
organizationId: '123',
2100+
variables: {searchTerm},
2101+
unauthorizedHandler: {
2102+
type: 'token_refresh',
2103+
handler: expect.any(Function),
2104+
},
2105+
})
2106+
2107+
// The deployed query AST must include both filters with the right shape so
2108+
// BP doesn't return inactive/cancelled/deleted stores. Mirrors the filter
2109+
// pattern already in production for the Dev Dashboard dev-stores list.
2110+
const accessibleShopsField = (
2111+
ListAppDevStores as unknown as {
2112+
definitions: {selectionSet: {selections: {selectionSet: {selections: any[]}}[]}}[]
2113+
}
2114+
).definitions[0]!.selectionSet.selections[0]!.selectionSet.selections.find(
2115+
(selection: any) => selection.name?.value === 'accessibleShops',
2116+
)
2117+
const filtersArg = accessibleShopsField.arguments.find((arg: any) => arg.name.value === 'filters')
2118+
expect(filtersArg.value.kind).toBe('ListValue')
2119+
const filters = filtersArg.value.values.map((value: any) => {
2120+
const output: Record<string, string> = {}
2121+
for (const field of value.fields) output[field.name.value] = field.value.value
2122+
return output
2123+
})
2124+
expect(filters).toEqual([
2125+
{field: 'STORE_TYPE', operator: 'EQUALS', value: 'app_development'},
2126+
{field: 'STORE_STATUS', operator: 'EQUALS', value: 'ACTIVE'},
2127+
])
2128+
2129+
expect(result.hasMorePages).toBe(false)
2130+
expect(result.stores).toHaveLength(1)
2131+
expect(result.stores[0]).toMatchObject({
2132+
shopName: 'My Active Store',
2133+
shopDomain: 'my-active-store.myshopify.com',
2134+
provisionable: true,
2135+
storeType: 'APP_DEVELOPMENT',
2136+
})
2137+
})
2138+
2139+
test('throws when the organization is not found', async () => {
2140+
// Given
2141+
vi.mocked(businessPlatformOrganizationsRequestDoc).mockResolvedValueOnce({organization: null})
2142+
2143+
// When
2144+
const client = AppManagementClient.getInstance()
2145+
client.businessPlatformToken = () => Promise.resolve('business-platform-token')
2146+
2147+
// Then
2148+
await expect(client.devStoresForOrg('gid://shopify/Organization/123')).rejects.toThrow('No organization found')
2149+
})
2150+
2151+
test('returns an empty list when no accessible stores match the filters', async () => {
2152+
// Given a deleted/inactive store would be filtered out by STORE_STATUS=ACTIVE,
2153+
// so BP returns an empty edges list to the CLI.
2154+
vi.mocked(businessPlatformOrganizationsRequestDoc).mockResolvedValueOnce({
2155+
organization: {
2156+
id: 'gid://shopify/Organization/123',
2157+
name: 'Org 123',
2158+
accessibleShops: {edges: [], pageInfo: {hasNextPage: false}},
2159+
currentUser: {organizationPermissions: []},
2160+
},
2161+
})
2162+
2163+
// When
2164+
const client = AppManagementClient.getInstance()
2165+
client.businessPlatformToken = () => Promise.resolve('business-platform-token')
2166+
const result = await client.devStoresForOrg('gid://shopify/Organization/123')
2167+
2168+
// Then
2169+
expect(result.stores).toEqual([])
2170+
expect(result.hasMorePages).toBe(false)
2171+
})
2172+
})

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,7 @@ export class AppManagementClient implements DeveloperPlatformClient {
856856
organizationId: String(numberFromGid(orgId)),
857857
variables: {
858858
domain: shopDomain,
859-
filters: [{field: 'STORE_TYPE' as const, operator: 'EQUALS' as const, value: storeType.toLowerCase()}],
859+
filters: storeByDomainFilters(storeType),
860860
},
861861
}),
862862
),
@@ -1486,6 +1486,13 @@ function toUserError(err: CreateAppVersionMutation['appVersionCreate']['userErro
14861486
return {...err, details}
14871487
}
14881488

1489+
// Keep explicit domain lookup broader than ListAppDevStores for now.
1490+
// If APP_DEVELOPMENT lookups also need to exclude deleted/inactive stores here,
1491+
// add STORE_STATUS=ACTIVE only for that store type and cover mixed storeTypes callers.
1492+
function storeByDomainFilters(storeType: Store) {
1493+
return [{field: 'STORE_TYPE' as const, operator: 'EQUALS' as const, value: storeType.toLowerCase()}]
1494+
}
1495+
14891496
function isStoreProvisionable(permissions: string[]) {
14901497
return permissions.includes('ondemand_access_to_stores')
14911498
}

0 commit comments

Comments
 (0)