Skip to content

Commit b8f02df

Browse files
committed
app calls Bulk Operation Query API
1 parent ce67b98 commit b8f02df

File tree

3 files changed

+145
-4
lines changed

3 files changed

+145
-4
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {gql} from 'graphql-request'
2+
3+
// eslint-disable-next-line @shopify/cli/no-inline-graphql
4+
export const BulkOperationRunQuery = gql`
5+
mutation BulkOperationRunQuery($query: String!) {
6+
bulkOperationRunQuery(query: $query) {
7+
bulkOperation {
8+
id
9+
status
10+
errorCode
11+
createdAt
12+
objectCount
13+
fileSize
14+
url
15+
}
16+
userErrors {
17+
field
18+
message
19+
}
20+
}
21+
}
22+
`
23+
24+
export interface BulkOperationRunQuerySchema {
25+
bulkOperationRunQuery: {
26+
bulkOperation: {
27+
id: string
28+
status: string
29+
errorCode: string | null
30+
createdAt: string
31+
objectCount: string
32+
fileSize: string
33+
url: string | null
34+
} | null
35+
userErrors: {
36+
field: string[] | null
37+
message: string
38+
}[]
39+
}
40+
}

packages/app/src/cli/commands/app/execute.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import {appFlags} from '../../flags.js'
22
import AppLinkedCommand, {AppLinkedCommandOutput} from '../../utilities/app-linked-command.js'
33
import {linkedAppContext} from '../../services/app-context.js'
4+
import {storeContext} from '../../services/store-context.js'
5+
import {runBulkOperationQuery} from '../../services/bulk-operations.js'
6+
import {Flags} from '@oclif/core'
47
import {globalFlags} from '@shopify/cli-kit/node/cli'
5-
import {renderSuccess} from '@shopify/cli-kit/node/ui'
8+
import {renderSuccess, renderInfo, renderWarning} from '@shopify/cli-kit/node/ui'
9+
import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn'
10+
import {outputContent, outputToken} from '@shopify/cli-kit/node/output'
611

712
export default class Execute extends AppLinkedCommand {
813
static summary = 'Execute bulk operations.'
@@ -14,6 +19,18 @@ export default class Execute extends AppLinkedCommand {
1419
static flags = {
1520
...globalFlags,
1621
...appFlags,
22+
query: Flags.string({
23+
char: 'q',
24+
description: 'The GraphQL query, as a string.',
25+
env: 'SHOPIFY_FLAG_QUERY',
26+
required: true,
27+
}),
28+
store: Flags.string({
29+
char: 's',
30+
description: 'Store URL. Must be an existing development or Shopify Plus sandbox store.',
31+
env: 'SHOPIFY_FLAG_STORE',
32+
parse: async (input) => normalizeStoreFqdn(input),
33+
}),
1734
}
1835

1936
async run(): Promise<AppLinkedCommandOutput> {
@@ -26,11 +43,57 @@ export default class Execute extends AppLinkedCommand {
2643
userProvidedConfigName: flags.config,
2744
})
2845

29-
renderSuccess({
30-
headline: 'App context loaded successfully!',
31-
body: `App: ${appContextResult.app.name}\nOrganization: ${appContextResult.organization.businessName}`,
46+
const store = await storeContext({
47+
appContextResult,
48+
storeFqdn: flags.store,
49+
forceReselectStore: flags.reset,
3250
})
3351

52+
renderInfo({
53+
headline: 'Starting bulk operation.',
54+
body: `App: ${appContextResult.app.name}\nStore: ${store.shopDomain}`,
55+
})
56+
57+
const {result, errors} = await runBulkOperationQuery({
58+
storeFqdn: store.shopDomain,
59+
query: flags.query,
60+
})
61+
62+
if (errors && errors.length > 0) {
63+
const errorMessages = errors.map((error) => `${error.field?.join('.') ?? 'unknown'}: ${error.message}`).join('\n')
64+
renderWarning({
65+
headline: 'Bulk operation errors.',
66+
body: errorMessages,
67+
})
68+
return {app: appContextResult.app}
69+
}
70+
71+
if (result) {
72+
const infoSections = [
73+
{
74+
title: 'Bulk Operation Created',
75+
body: [
76+
{
77+
list: {
78+
items: [
79+
outputContent`ID: ${outputToken.cyan(result.id)}`.value,
80+
outputContent`Status: ${outputToken.yellow(result.status)}`.value,
81+
outputContent`Created: ${outputToken.gray(result.createdAt)}`.value,
82+
],
83+
},
84+
},
85+
],
86+
},
87+
]
88+
89+
renderInfo({customSections: infoSections})
90+
91+
renderSuccess({
92+
headline: 'Bulk operation started successfully!',
93+
body: 'Congrats!',
94+
})
95+
}
96+
3497
return {app: appContextResult.app}
3598
}
3699
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {BulkOperationRunQuery, BulkOperationRunQuerySchema} from '../api/graphql/bulk_operation_run_query.js'
2+
import {adminRequest} from '@shopify/cli-kit/node/api/admin'
3+
import {AdminSession, ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/session'
4+
5+
export interface BulkOperationRunQueryOptions {
6+
storeFqdn: string
7+
query: string
8+
}
9+
10+
export type BulkOperationResult = NonNullable<BulkOperationRunQuerySchema['bulkOperationRunQuery']['bulkOperation']>
11+
export type BulkOperationError = BulkOperationRunQuerySchema['bulkOperationRunQuery']['userErrors'][number]
12+
13+
/**
14+
* Executes a bulk operation query against the Shopify Admin API.
15+
* The operation runs asynchronously in the background.
16+
*/
17+
export async function runBulkOperationQuery(
18+
options: BulkOperationRunQueryOptions,
19+
): Promise<{result?: BulkOperationResult; errors?: BulkOperationError[]}> {
20+
const {storeFqdn, query} = options
21+
const adminSession: AdminSession = await ensureAuthenticatedAdmin(storeFqdn)
22+
const response: BulkOperationRunQuerySchema = await adminRequest(BulkOperationRunQuery, adminSession, {query})
23+
24+
if (response.bulkOperationRunQuery.userErrors.length > 0) {
25+
return {
26+
errors: response.bulkOperationRunQuery.userErrors,
27+
}
28+
}
29+
30+
const bulkOperation = response.bulkOperationRunQuery.bulkOperation
31+
if (bulkOperation) {
32+
return {result: bulkOperation}
33+
}
34+
35+
return {
36+
errors: [{field: null, message: 'No bulk operation was created'}],
37+
}
38+
}

0 commit comments

Comments
 (0)