Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate theme delete/update/publish to GraphQL #4623

Merged
merged 1 commit into from
Oct 30, 2024

Conversation

lucyxiang
Copy link
Contributor

@lucyxiang lucyxiang commented Oct 9, 2024

WHY are these changes introduced?

Following the introduction of theme admin GraphQL endpoints, port the CLI to use GraphQL.

Extending what @catlee started here #4541

WHAT is this pull request doing?

Migrate themeDelete, themeUpdate, and themePublish to use the admin graphql for both regular sessions and theme access sessions. Renamed uses of delete/update/publishTheme to themeDelete/Update/Publish for consistency.

Change themeDelete to return boolean (true on success, false on failure) instead of a theme.

To ship after Chris' code gen PR (shipped).

How to test your changes?

shopify theme delete
Screenshot 2024-10-16 at 15 51 19

shopify theme publish
Screenshot 2024-10-16 at 15 50 52

shopify theme rename
Screenshot 2024-10-16 at 15 50 32

Measuring impact

How do we know this change was effective? Please choose one:

  • n/a - this doesn't need measurement, e.g. a linting rule or a bug-fix
  • Existing analytics will cater for this addition
  • PR includes analytics changes to measure impact

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes

@lucyxiang lucyxiang requested a review from a team as a code owner October 9, 2024 19:14
@lucyxiang lucyxiang requested review from jamieguerrero and gordonhirsch and removed request for a team October 9, 2024 19:14
Copy link
Contributor

github-actions bot commented Oct 9, 2024

We detected some changes at either packages/*/src or packages/cli-kit/assets/cli-ruby/** and there are no updates in the .changeset.
If the changes are user-facing, run "pnpm changeset add" to track your changes and include them in the next release CHANGELOG.

@lucyxiang lucyxiang force-pushed the migrate-theme-delete-theme-publish-to-gql branch from aae05b3 to ca94b5a Compare October 15, 2024 20:49
packages/cli-kit/src/public/node/themes/api.ts Outdated Show resolved Hide resolved
const response = await request('POST', `/themes`, session, params)
return buildTheme(response.json.theme)
return buildTheme({
id: parseInt((theme.id as unknown as string).split('/').pop() as string, 10),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a follow-up, what do you think of swapping over the Theme object here to use gids?

we need to convert back and forth between ints and gids in quite a few places. will we still need the plain int id anywhere after we're done migrating?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm kind of shocked we don't already have some composeGid and parseGid functions in the repo. Would be good to add them as a part of this transition I think. There's a parseGid you can rip from Hydrogen if you want: https://github.com/Shopify/hydrogen/blob/main/packages/cli/src/lib/gid.ts

packages/cli-kit/src/public/node/themes/api.ts Outdated Show resolved Hide resolved
packages/cli-kit/src/public/node/themes/api.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@karreiro karreiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this PR, @lucyxiang! It's heading in an excellent direction :)

I've left just a comments about keeping the API layer compatible with the Theme Access app. Otherwise, we might potentially impact CI/CD workflows and users using that as an authentication method.

Also, I believe we could ignore the admin_schema.graphql file, following the reasoning in #4620 (comment).

Thanks again for this PR!

packages/cli-kit/src/public/node/themes/api.ts Outdated Show resolved Hide resolved
packages/cli-kit/src/public/node/themes/api.ts Outdated Show resolved Hide resolved
@lucyxiang lucyxiang force-pushed the migrate-theme-delete-theme-publish-to-gql branch 2 times, most recently from 8a56f60 to 6ba04ee Compare October 16, 2024 19:45
Copy link
Contributor

github-actions bot commented Oct 16, 2024

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements
72.43% (-0.11% 🔻)
8366/11550
🟡 Branches
68.8% (-0.19% 🔻)
4078/5927
🟡 Functions
71.87% (-0.22% 🔻)
2197/3057
🟡 Lines
72.73% (-0.11% 🔻)
7912/10878
Show new covered files 🐣
St.
File Statements Branches Functions Lines
🟢
... / theme_delete.ts
100% 100% 100% 100%
🟢
... / theme_publish.ts
100% 100% 100% 100%
🟢
... / theme_update.ts
100% 100% 100% 100%
Show files with reduced coverage 🔻
St.
File Statements Branches Functions Lines
🟡
... / api.ts
61.86% (-21.97% 🔻)
53.06% (-30.81% 🔻)
52% (-29.82% 🔻)
62.22% (-22.15% 🔻)
🟡
... / utils.ts
66.67% (+66.67% 🔼)
75% (-25% 🔻)
50% (+50% 🔼)
66.67% (+66.67% 🔼)

Test suite run success

1919 tests passing in 872 suites.

Report generated by 🧪jest coverage report action from a65ac51

@lucyxiang lucyxiang force-pushed the migrate-theme-delete-theme-publish-to-gql branch from 6ba04ee to 0824a14 Compare October 22, 2024 18:12
@lucyxiang lucyxiang requested a review from a team as a code owner October 22, 2024 18:12
@lucyxiang lucyxiang force-pushed the migrate-theme-delete-theme-publish-to-gql branch from 0824a14 to ae33c65 Compare October 22, 2024 18:20
Copy link
Contributor

We detected some changes at packages/*/src and there are no updates in the .changeset.
If the changes are user-facing, run "pnpm changeset add" to track your changes and include them in the next release CHANGELOG.

@lucyxiang lucyxiang force-pushed the migrate-theme-delete-theme-publish-to-gql branch from ae33c65 to 2fb46d3 Compare October 22, 2024 18:38
Copy link
Contributor

@graygilmore graygilmore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No 🎩 yet but looking good! Small concern around the types that are getting generated, would love to confirm that.

packages/cli-kit/src/public/node/api/admin.ts Outdated Show resolved Hide resolved
packages/cli-kit/src/public/node/api/admin.ts Outdated Show resolved Hide resolved
packages/cli-kit/src/public/node/api/admin.ts Outdated Show resolved Hide resolved
const response = await request('POST', `/themes`, session, params)
return buildTheme(response.json.theme)
return buildTheme({
id: parseInt((theme.id as unknown as string).split('/').pop() as string, 10),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm kind of shocked we don't already have some composeGid and parseGid functions in the repo. Would be good to add them as a part of this transition I think. There's a parseGid you can rip from Hydrogen if you want: https://github.com/Shopify/hydrogen/blob/main/packages/cli/src/lib/gid.ts

packages/cli-kit/src/public/node/themes/api.ts Outdated Show resolved Hide resolved
@EvilGenius13
Copy link
Contributor

Quick question: for the mutations, should they be in something like api/graphql/admin/mutations/theme_delete.graphql
instead of api/graphql/admin/queries/theme_delete.graphql since they are mutations and we might have queries at a later point?

@lucyxiang lucyxiang force-pushed the migrate-theme-delete-theme-publish-to-gql branch 3 times, most recently from 4ab70cf to 517c2bf Compare October 24, 2024 20:39
@lucyxiang lucyxiang force-pushed the migrate-theme-delete-theme-publish-to-gql branch from 517c2bf to e0976e9 Compare October 25, 2024 16:34
Comment on lines 102 to 104
} else {
// An unexpected error if neither theme nor userErrors are returned
unexpectedGraphQLError('Failed to update theme')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure when this would happen (no execution error but also not getting back the specified fields). I was tempted to not have this else but if it does happen, it's better we raise an error than not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻 ideally adminRequestDoc covers this case for all calls but we don't have to push that in this PR. I'm still convinced that the types in this repo aren't being generated quite right but I don't have time to poke at it yet. We'll just have to be extra defensive for now I think.

@@ -1,6 +1,6 @@
import {deleteThemes, renderDeprecatedArgsWarning} from './delete.js'
Copy link
Contributor Author

@lucyxiang lucyxiang Oct 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly renaming from here on down (all the changes in this directory)

@lucyxiang lucyxiang force-pushed the migrate-theme-delete-theme-publish-to-gql branch from e0976e9 to 555e722 Compare October 25, 2024 17:08
Copy link
Contributor

@graygilmore graygilmore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no 🎩 but code looks good!

return `gid://shopify/OnlineStoreTheme/${id}`
}

export function themeGidToId(gid: string): number {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: prefer parseGid to match other systems.

(nit in the same vein: themeGid => composeThemeGid but feel free to ignore)

}

export function themeGidToId(gid: string): number {
return parseInt((gid as unknown as string).split('/').pop() as string, 10)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe right now all of our resources should contain a numeric ID on the end of the GID but it's not necessarily a guarantee. I think parseInt() can probably also handle query params (e.g. gid://shopify/Example/1?foo=3&bar=baz but it feels wrong.

I know that this probably works in all scenarios but I wonder if it'd be better feedback if we threw an error if we ever received a GID that wasn't valid. Here's a regex that we've used in other projects:

Suggested change
return parseInt((gid as unknown as string).split('/').pop() as string, 10)
const id = `/${gid}`;
const matches = /\/(\w[\w-]*)(?:\?(.*))*$/.exec(id);
if (matches && matches[1] !== undefined) {
return matches[1];
}
throw new Error(`Invalid GID: ${gid}`);

And the one I mentioned before in Hydrogen: https://github.com/Shopify/hydrogen/blob/main/packages/cli/src/lib/gid.ts

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to go with what hydrogen uses, it seems more foolproof which I like. Thank you for sharing Gray!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this link in your previous comment, my bad 🙈 didn't mean to ignore it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have all the old REST code removed, I think we can remove most of the GID parsing / generation.

These can mostly be treated as opaque strings, with the exception of how the theme list command outputs them, or passing in themes by id on the command line.

Comment on lines 102 to 104
} else {
// An unexpected error if neither theme nor userErrors are returned
unexpectedGraphQLError('Failed to update theme')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻 ideally adminRequestDoc covers this case for all calls but we don't have to push that in this PR. I'm still convinced that the types in this repo aren't being generated quite right but I don't have time to poke at it yet. We'll just have to be extra defensive for now I think.

}

export async function publishTheme(id: number, session: AdminSession): Promise<Theme | undefined> {
return updateTheme(id, {role: 'main'}, session)
export async function themePublish(id: number, session: AdminSession): Promise<Theme | undefined> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚲 🖌️ (nit) feel free to ignore completely, just sharing how I might have written this now that I see your new approach!

export async function themePublish(id: number, session: AdminSession): Promise<Theme | undefined> {
  try {
    const {themePublish} = await adminRequestDoc(ThemePublish, session, {id: themeGid(id)})

    if (!themePublish) throw new Error('')

    const {theme, userErrors} = themePublish

    if (userErrors.length) {
      throw new Error(userErrors.map((error) => error.message).join(', '))
    }

    if (!theme) throw new Error('')

    return buildTheme({
      id: themeGidToId(theme.id),
      name: theme.name,
      role: theme.role.toLowerCase(),
    })
  } catch (error: unknown) {
    if (error instanceof Error) {
      throw new AbortError(error.message)
    }
    throw new AbortError('An unknown error occurred')
  }
}

I like the try/catch here because it means we can just throw when we don't like the state and we don't end up in nested if/else statements.

Copy link
Contributor Author

@lucyxiang lucyxiang Oct 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used a mix of yours and guilherme's suggestion. I don't love throwing Error('') and then populating it later , but agreed with less if/else statements.

Copy link
Contributor

@karreiro karreiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this PR, @lucyxiang! LGTM and works as expected as well 🎩 I've left two minor comments that I'd like to know your thoughts :)

Thanks again for this PR!

packages/cli-kit/src/public/node/themes/api.ts Outdated Show resolved Hide resolved
@lucyxiang lucyxiang force-pushed the migrate-theme-delete-theme-publish-to-gql branch 2 times, most recently from 4dc5b4a to 6eaa4ac Compare October 29, 2024 14:21
@lucyxiang lucyxiang force-pushed the migrate-theme-delete-theme-publish-to-gql branch from 6eaa4ac to a65ac51 Compare October 29, 2024 14:45
Copy link
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

packages/cli-kit/dist/cli/api/graphql/admin/generated/theme_delete.d.ts
import * as Types from './types.js';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type ThemeDeleteMutationVariables = Types.Exact<{
    id: Types.Scalars['ID']['input'];
}>;
export type ThemeDeleteMutation = {
    themeDelete?: {
        deletedThemeId?: string | null;
        userErrors: {
            field?: string[] | null;
            message: string;
        }[];
    } | null;
};
export declare const ThemeDelete: DocumentNode<ThemeDeleteMutation, Types.Exact<{
    id: Types.Scalars['ID']['input'];
}>>;
packages/cli-kit/dist/cli/api/graphql/admin/generated/theme_publish.d.ts
import * as Types from './types.js';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type ThemePublishMutationVariables = Types.Exact<{
    id: Types.Scalars['ID']['input'];
}>;
export type ThemePublishMutation = {
    themePublish?: {
        theme?: {
            id: string;
            name: string;
            role: Types.ThemeRole;
        } | null;
        userErrors: {
            field?: string[] | null;
            message: string;
        }[];
    } | null;
};
export declare const ThemePublish: DocumentNode<ThemePublishMutation, Types.Exact<{
    id: Types.Scalars['ID']['input'];
}>>;
packages/cli-kit/dist/cli/api/graphql/admin/generated/theme_update.d.ts
import * as Types from './types.js';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type ThemeUpdateMutationVariables = Types.Exact<{
    id: Types.Scalars['ID']['input'];
    input: Types.OnlineStoreThemeInput;
}>;
export type ThemeUpdateMutation = {
    themeUpdate?: {
        theme?: {
            id: string;
            name: string;
            role: Types.ThemeRole;
        } | null;
        userErrors: {
            field?: string[] | null;
            message: string;
        }[];
    } | null;
};
export declare const ThemeUpdate: DocumentNode<ThemeUpdateMutation, Types.Exact<{
    id: Types.Scalars['ID']['input'];
    input: Types.OnlineStoreThemeInput;
}>>;

Existing type declarations

packages/cli-kit/dist/public/node/themes/api.d.ts
@@ -9,14 +9,6 @@ export declare function fetchThemeAsset(id: number, key: Key, session: AdminSess
 export declare function deleteThemeAsset(id: number, key: Key, session: AdminSession): Promise<boolean>;
 export declare function bulkUploadThemeAssets(id: number, assets: AssetParams[], session: AdminSession): Promise<Result[]>;
 export declare function fetchChecksums(id: number, session: AdminSession): Promise<Checksum[]>;
-interface UpgradeThemeOptions {
-    fromTheme: number;
-    toTheme: number;
-    script?: string;
-    session: AdminSession;
-}
-export declare function upgradeTheme(upgradeOptions: UpgradeThemeOptions): Promise<Theme | undefined>;
-export declare function updateTheme(id: number, params: ThemeParams, session: AdminSession): Promise<Theme | undefined>;
-export declare function publishTheme(id: number, session: AdminSession): Promise<Theme | undefined>;
-export declare function deleteTheme(id: number, session: AdminSession): Promise<Theme | undefined>;
-export {};
\ No newline at end of file
+export declare function themeUpdate(id: number, params: ThemeParams, session: AdminSession): Promise<Theme | undefined>;
+export declare function themePublish(id: number, session: AdminSession): Promise<Theme | undefined>;
+export declare function themeDelete(id: number, session: AdminSession): Promise<boolean | undefined>;
\ No newline at end of file
packages/cli-kit/dist/public/node/themes/utils.d.ts
@@ -4,4 +4,6 @@ export declare const LIVE_THEME_ROLE = "live";
 export declare const UNPUBLISHED_THEME_ROLE = "unpublished";
 export type Role = typeof DEVELOPMENT_THEME_ROLE | typeof LIVE_THEME_ROLE | typeof UNPUBLISHED_THEME_ROLE;
 export declare function isDevelopmentTheme(theme: Theme): boolean;
-export declare function promptThemeName(message: string): Promise<string>;
\ No newline at end of file
+export declare function promptThemeName(message: string): Promise<string>;
+export declare function composeThemeGid(id: number): string;
+export declare function parseGid(gid: string): number;
\ No newline at end of file

@lucyxiang
Copy link
Contributor Author

/shipit

Copy link
Contributor

@gordonhirsch gordonhirsch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay.

@lucyxiang lucyxiang added this pull request to the merge queue Oct 30, 2024
Merged via the queue into main with commit ce52ab9 Oct 30, 2024
27 checks passed
@lucyxiang lucyxiang deleted the migrate-theme-delete-theme-publish-to-gql branch October 30, 2024 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants