diff --git a/src/lib/featureFlags.ts b/src/lib/featureFlags.ts index 41b0dd4ee7..734954c99d 100644 --- a/src/lib/featureFlags.ts +++ b/src/lib/featureFlags.ts @@ -12,7 +12,6 @@ const FEATURE_FLAGS_LIST = [ "diamond_blurhash-enabled-globally", "onyx_enable-home-view-section-featured-fairs", "diamond_home-view-marketing-collection-categories", - "emerald_home-view-tasks-section", "onyx_experiment_home_view_test", ] as const diff --git a/src/lib/loaders/loaders_with_authentication/gravity.ts b/src/lib/loaders/loaders_with_authentication/gravity.ts index 557c3a031b..e3ea3aa84b 100644 --- a/src/lib/loaders/loaders_with_authentication/gravity.ts +++ b/src/lib/loaders/loaders_with_authentication/gravity.ts @@ -256,6 +256,21 @@ export default (accessToken, userID, opts) => { {}, { method: "PUT" } ), + updateViewingRoomLoader: gravityLoader( + (id) => `viewing_room/${id}`, + {}, + { method: "PUT" } + ), + updateViewingRoomArtworksLoader: gravityLoader( + (id) => `viewing_room/${id}/viewing_room_artworks`, + {}, + { method: "PUT" } + ), + updateViewingRoomSubsectionsLoader: gravityLoader( + (id) => `viewing_room/${id}/subsections`, + {}, + { method: "PUT" } + ), deleteHeroUnitLoader: gravityLoader( (id) => `hero_units/${id}`, {}, @@ -289,6 +304,11 @@ export default (accessToken, userID, opts) => { {}, { method: "DELETE" } ), + deleteViewingRoomLoader: gravityLoader( + (id) => `viewing_room/${id}`, + {}, + { method: "DELETE" } + ), dislikeArtworkLoader: gravityLoader( (id) => `collection/disliked-artwork/artwork/${id}`, {}, diff --git a/src/lib/stitching/gravity/schema.ts b/src/lib/stitching/gravity/schema.ts index 5c5e2f1012..8cd81eef66 100644 --- a/src/lib/stitching/gravity/schema.ts +++ b/src/lib/stitching/gravity/schema.ts @@ -58,13 +58,30 @@ export const executableGravitySchema = () => { duplicatedTypes.push("CreateViewingRoomPayload") duplicatedTypes.push("CreateViewingRoomInput") + duplicatedTypes.push("DeleteViewingRoomInput") + duplicatedTypes.push("DeleteViewingRoomPayload") duplicatedTypes.push("ViewingRoomOrErrorsUnion") duplicatedTypes.push("ViewingRoomAttributes") + duplicatedTypes.push("UpdateViewingRoomPayload") + duplicatedTypes.push("UpdateViewingRoomArtworksInput") + duplicatedTypes.push("PublishViewingRoomInput") + duplicatedTypes.push("PublishViewingRoomPayload") + duplicatedTypes.push("UnpublishViewingRoomInput") + duplicatedTypes.push("UnpublishViewingRoomPayload") + duplicatedTypes.push("UpdateViewingRoomArtworksPayload") + duplicatedTypes.push("UpdateViewingRoomSubsectionsInput") + duplicatedTypes.push("UpdateViewingRoomSubsectionsPayload") } const excludedMutations: string[] = [] if (config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA) { excludedMutations.push("createViewingRoom") + excludedMutations.push("deleteViewingRoom") + excludedMutations.push("publishViewingRoom") + excludedMutations.push("unpublishViewingRoom") + excludedMutations.push("updateViewingRoom") + excludedMutations.push("updateViewingRoomArtworks") + excludedMutations.push("updateViewingRoomSubsections") } // Types which come from Gravity that are not (yet) needed in MP. diff --git a/src/schema/v2/artwork/__tests__/artwork.test.js b/src/schema/v2/artwork/__tests__/artwork.test.js index 1addf27f07..18a9234838 100644 --- a/src/schema/v2/artwork/__tests__/artwork.test.js +++ b/src/schema/v2/artwork/__tests__/artwork.test.js @@ -5129,6 +5129,7 @@ describe("Artwork type", () => { describe("runningShow", () => { it("returns the show or fair if the artwork id is in a running show or fair", async () => { artwork.purchasable = true + artwork.show_ids = ["show-id"] context.showsLoader.mockResolvedValue([ { name: "Test Show", diff --git a/src/schema/v2/artwork/collectorSignals.ts b/src/schema/v2/artwork/collectorSignals.ts index eda560f721..1f019f160c 100644 --- a/src/schema/v2/artwork/collectorSignals.ts +++ b/src/schema/v2/artwork/collectorSignals.ts @@ -185,9 +185,12 @@ export const CollectorSignals: GraphQLFieldConfig = { type: Show.type, description: "Most recent running Show or Fair booth the artwork is currently in, sorted by relevance", - resolve: async (artwork, {}, ctx) => { + resolve: async ({ show_ids }, {}, ctx) => { + if (!show_ids || show_ids.length === 0) { + return null + } const showOrFair = await await ctx.showsLoader({ - artwork: artwork._id, + id: show_ids, size: 1, status: "running", has_location: true, diff --git a/src/schema/v2/artwork/context.ts b/src/schema/v2/artwork/context.ts index 1fb64bf31c..798795677c 100644 --- a/src/schema/v2/artwork/context.ts +++ b/src/schema/v2/artwork/context.ts @@ -34,7 +34,7 @@ const Context: GraphQLFieldConfig = { type: ArtworkContextType, description: "Returns the associated Fair/Sale/Show", resolve: ( - { id, sale_ids }, + { id, sale_ids, show_ids }, _options, { salesLoader, relatedFairsLoader, showsLoader } ) => { @@ -58,21 +58,24 @@ const Context: GraphQLFieldConfig = { return assign({ context_type: "Fair" }, fair) }) - const show_promise = showsLoader({ - artwork: id, - size: 1, - at_a_fair: false, - }) - .then(first) - .then((show) => { - if (!show) return null - return assign({ context_type: "Show" }, show) + let showPromise + if (show_ids && show_ids.length > 0) { + showPromise = showsLoader({ + id: show_ids, + size: 1, + at_a_fair: false, }) + .then(first) + .then((show) => { + if (!show) return null + return assign({ context_type: "Show" }, show) + }) + } return Promise.all([ sale_promise || Promise.resolve(null), fair_promise, - show_promise, + showPromise || Promise.resolve(null), ]).then(choose) }, } diff --git a/src/schema/v2/homeView/__tests__/HomeView.test.ts b/src/schema/v2/homeView/__tests__/HomeView.test.ts index dbac2147e6..e9b436dba5 100644 --- a/src/schema/v2/homeView/__tests__/HomeView.test.ts +++ b/src/schema/v2/homeView/__tests__/HomeView.test.ts @@ -174,6 +174,14 @@ describe("homeView", () => { expect(homeView.sectionsConnection).toMatchInlineSnapshot(` { "edges": [ + { + "node": { + "__typename": "HomeViewSectionTasks", + "component": { + "title": "Act Now", + }, + }, + }, { "node": { "__typename": "HomeViewSectionActivity", diff --git a/src/schema/v2/homeView/sections/Tasks.ts b/src/schema/v2/homeView/sections/Tasks.ts index 8aea759484..2755c0edbd 100644 --- a/src/schema/v2/homeView/sections/Tasks.ts +++ b/src/schema/v2/homeView/sections/Tasks.ts @@ -13,7 +13,6 @@ export const Tasks: HomeViewSection = { component: { title: "Act Now", }, - featureFlag: "emerald_home-view-tasks-section", requiresAuthentication: true, resolver: withHomeViewTimeout(async (_parent, args, { meTasksLoader }) => { if (!meTasksLoader) return null diff --git a/src/schema/v2/homeView/sections/__tests__/Tasks.test.ts b/src/schema/v2/homeView/sections/__tests__/Tasks.test.ts index 1b3e0c80df..9ce8955e35 100644 --- a/src/schema/v2/homeView/sections/__tests__/Tasks.test.ts +++ b/src/schema/v2/homeView/sections/__tests__/Tasks.test.ts @@ -1,4 +1,3 @@ -import { isFeatureFlagEnabled } from "lib/featureFlags" import gql from "lib/gql" import { runQuery } from "schema/v2/test/utils" @@ -6,15 +5,7 @@ jest.mock("lib/featureFlags", () => ({ isFeatureFlagEnabled: jest.fn(() => true), })) -const mockIsFeatureFlagEnabled = isFeatureFlagEnabled as jest.Mock - describe("HomeViewSectionTasks", () => { - beforeAll(() => { - mockIsFeatureFlagEnabled.mockImplementation((flag: string) => { - if (flag === "emerald_home-view-tasks-section") return true - }) - }) - it("returns the section's metadata", async () => { const query = gql` { @@ -133,33 +124,4 @@ describe("HomeViewSectionTasks", () => { } `) }) - - describe("when the feature flag is disabled", () => { - beforeAll(() => { - mockIsFeatureFlagEnabled.mockImplementation((flag: string) => { - if (flag === "emerald_home-view-tasks-section") return false - }) - }) - - it("throws an error when accessed by id", async () => { - const query = gql` - { - homeView { - section(id: "home-view-section-tasks") { - __typename - component { - title - } - } - } - } - ` - - const context = {} - - await expect(runQuery(query, context)).rejects.toThrow( - "Section is not displayable: home-view-section-tasks" - ) - }) - }) }) diff --git a/src/schema/v2/schema.ts b/src/schema/v2/schema.ts index 5603ebf3d7..2cdcd2671c 100644 --- a/src/schema/v2/schema.ts +++ b/src/schema/v2/schema.ts @@ -261,6 +261,12 @@ import { deliverSecondFactorMutation } from "./me/secondFactors/mutations/delive import { enableSecondFactorMutation } from "./me/secondFactors/mutations/enableSecondFactor" import { createAndSendBackupSecondFactorMutation } from "./users/createAndSendBackupSecondFactorMutation" import { createViewingRoomMutation } from "./viewingRooms/mutations/createViewingRoomMutation" +import { updateViewingRoomMutation } from "./viewingRooms/mutations/updateViewingRoomMutation" +import { deleteViewingRoomMutation } from "./viewingRooms/mutations/deleteViewingRoomMutation" +import { publishViewingRoomMutation } from "./viewingRooms/mutations/publishViewingRoomMutation" +import { unpublishViewingRoomMutation } from "./viewingRooms/mutations/unpublishViewingRoomMutation" +import { updateViewingRoomArtworksMutation } from "./viewingRooms/mutations/updateViewingRoomArtworks" +import { updateViewingRoomSubsectionsMutation } from "./viewingRooms/mutations/updateViewingRoomSubsections" const viewingRoomUnstitchedRootField = config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA ? { @@ -272,6 +278,12 @@ const viewingRoomUnstitchedRootField = config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA const viewingRoomsMutations = config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA ? { createViewingRoom: createViewingRoomMutation, + deleteViewingRoom: deleteViewingRoomMutation, + publishViewingRoom: publishViewingRoomMutation, + unpublishViewingRoom: unpublishViewingRoomMutation, + updateViewingRoom: updateViewingRoomMutation, + updateViewingRoomArtworks: updateViewingRoomArtworksMutation, + updateViewingRoomSubsections: updateViewingRoomSubsectionsMutation, } : ({} as any) diff --git a/src/schema/v2/viewingRoomSubsection.ts b/src/schema/v2/viewingRoomSubsection.ts index 19a2c37251..bdf9726173 100644 --- a/src/schema/v2/viewingRoomSubsection.ts +++ b/src/schema/v2/viewingRoomSubsection.ts @@ -28,6 +28,10 @@ export const ViewingRoomSubsectionType = new GraphQLObjectType< image: { type: GravityARImageType, }, + imageURL: { + type: GraphQLString, + resolve: ({ image_url }) => image_url, + }, title: { type: GraphQLString, }, diff --git a/src/schema/v2/viewingRooms/mutations/ARImageInput.ts b/src/schema/v2/viewingRooms/mutations/ARImageInput.ts new file mode 100644 index 0000000000..584cea33e3 --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/ARImageInput.ts @@ -0,0 +1,10 @@ +import { GraphQLID, GraphQLInputObjectType, GraphQLNonNull } from "graphql" + +export const ARImageInputType = new GraphQLInputObjectType({ + name: "ARImageInput", + fields: { + internalID: { + type: new GraphQLNonNull(GraphQLID), + }, + }, +}) diff --git a/src/schema/v2/viewingRooms/mutations/__tests__/deleteViewingRoomMutation.test.ts b/src/schema/v2/viewingRooms/mutations/__tests__/deleteViewingRoomMutation.test.ts new file mode 100644 index 0000000000..d013df3b31 --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/__tests__/deleteViewingRoomMutation.test.ts @@ -0,0 +1,47 @@ +import config from "config" +import gql from "lib/gql" +import { runAuthenticatedQuery } from "schema/v2/test/utils" + +describe("deleteViewingRoomMutation", () => { + const mockDeleteViewingRoomLoader = jest.fn() + + const context = { + deleteViewingRoomLoader: mockDeleteViewingRoomLoader, + } + + const viewingRoomData = { + id: "viewing-room-id", + } + + beforeAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = true + }) + + afterAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = false + }) + + beforeEach(() => { + mockDeleteViewingRoomLoader.mockResolvedValue( + Promise.resolve(viewingRoomData) + ) + }) + + afterEach(() => { + mockDeleteViewingRoomLoader.mockReset() + }) + + const mutation = gql` + mutation { + deleteViewingRoom(input: { viewingRoomID: "viewing-room-id" }) { + __typename + } + } + ` + + it("correctly calls the deleteViewingRoomLoader", async () => { + await runAuthenticatedQuery(mutation, context) + + expect(mockDeleteViewingRoomLoader).toHaveBeenCalledWith("viewing-room-id") + }) +}) diff --git a/src/schema/v2/viewingRooms/mutations/__tests__/publishViewingRoomMutation.test.ts b/src/schema/v2/viewingRooms/mutations/__tests__/publishViewingRoomMutation.test.ts new file mode 100644 index 0000000000..05513e1849 --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/__tests__/publishViewingRoomMutation.test.ts @@ -0,0 +1,52 @@ +import config from "config" +import gql from "lib/gql" +import { runAuthenticatedQuery } from "schema/v2/test/utils" + +describe("publishViewingRoomMutation", () => { + const mockUpdateViewingRoomLoader = jest.fn() + + const context = { + updateViewingRoomLoader: mockUpdateViewingRoomLoader, + } + + const viewingRoomData = { + id: "viewing-room-id", + } + + beforeAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = true + }) + + afterAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = false + }) + + beforeEach(() => { + mockUpdateViewingRoomLoader.mockResolvedValue( + Promise.resolve(viewingRoomData) + ) + }) + + afterEach(() => { + mockUpdateViewingRoomLoader.mockReset() + }) + + const mutation = gql` + mutation { + publishViewingRoom(input: { viewingRoomID: "viewing-room-id" }) { + __typename + } + } + ` + + it("correctly calls the updateViewingRoomLoader", async () => { + await runAuthenticatedQuery(mutation, context) + + expect(mockUpdateViewingRoomLoader).toHaveBeenCalledWith( + "viewing-room-id", + { + published: true, + } + ) + }) +}) diff --git a/src/schema/v2/viewingRooms/mutations/__tests__/unpublishViewingRoomMutation.test.ts b/src/schema/v2/viewingRooms/mutations/__tests__/unpublishViewingRoomMutation.test.ts new file mode 100644 index 0000000000..7595d6946c --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/__tests__/unpublishViewingRoomMutation.test.ts @@ -0,0 +1,52 @@ +import config from "config" +import gql from "lib/gql" +import { runAuthenticatedQuery } from "schema/v2/test/utils" + +describe("unpublishViewingRoomMutation", () => { + const mockUpdateViewingRoomLoader = jest.fn() + + const context = { + updateViewingRoomLoader: mockUpdateViewingRoomLoader, + } + + const viewingRoomData = { + id: "viewing-room-id", + } + + beforeAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = true + }) + + afterAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = false + }) + + beforeEach(() => { + mockUpdateViewingRoomLoader.mockResolvedValue( + Promise.resolve(viewingRoomData) + ) + }) + + afterEach(() => { + mockUpdateViewingRoomLoader.mockReset() + }) + + const mutation = gql` + mutation { + unpublishViewingRoom(input: { viewingRoomID: "viewing-room-id" }) { + __typename + } + } + ` + + it("correctly calls the updateViewingRoomLoader", async () => { + await runAuthenticatedQuery(mutation, context) + + expect(mockUpdateViewingRoomLoader).toHaveBeenCalledWith( + "viewing-room-id", + { + published: false, + } + ) + }) +}) diff --git a/src/schema/v2/viewingRooms/mutations/__tests__/updateViewingRoomArtworksMutation.test.ts b/src/schema/v2/viewingRooms/mutations/__tests__/updateViewingRoomArtworksMutation.test.ts new file mode 100644 index 0000000000..96e183b6ac --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/__tests__/updateViewingRoomArtworksMutation.test.ts @@ -0,0 +1,74 @@ +import config from "config" +import gql from "lib/gql" +import { runAuthenticatedQuery } from "schema/v2/test/utils" + +describe("updateViewingRoomArtworksMutation", () => { + const mockUpdateViewingRoomArtworksLoader = jest.fn() + + const context = { + updateViewingRoomArtworksLoader: mockUpdateViewingRoomArtworksLoader, + } + + beforeAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = true + }) + + afterAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = false + }) + + beforeEach(() => { + mockUpdateViewingRoomArtworksLoader.mockResolvedValue( + Promise.resolve({ + artwork_ids: ["artwork-1"], + }) + ) + }) + + afterEach(() => { + mockUpdateViewingRoomArtworksLoader.mockReset() + }) + + const mutation = gql` + mutation { + updateViewingRoomArtworks( + input: { + viewingRoomID: "viewing-room-id" + artworks: [ + { artworkID: "artwork-1", position: 0 } + { artworkID: "artwork-2", delete: true } + ] + } + ) { + __typename + + artworkIDs + } + } + ` + + it("correctly calls the updateViewingRoomArtworksLoader", async () => { + const result = await runAuthenticatedQuery(mutation, context) + + expect(mockUpdateViewingRoomArtworksLoader).toHaveBeenCalledWith( + "viewing-room-id", + { + artworks: [ + { artwork_id: "artwork-1", position: 0 }, + { artwork_id: "artwork-2", delete: true }, + ], + } + ) + + expect(result).toMatchInlineSnapshot(` + { + "updateViewingRoomArtworks": { + "__typename": "updateViewingRoomArtworksPayload", + "artworkIDs": [ + "artwork-1", + ], + }, + } + `) + }) +}) diff --git a/src/schema/v2/viewingRooms/mutations/__tests__/updateViewingRoomMutation.test.ts b/src/schema/v2/viewingRooms/mutations/__tests__/updateViewingRoomMutation.test.ts new file mode 100644 index 0000000000..fc3bc866bd --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/__tests__/updateViewingRoomMutation.test.ts @@ -0,0 +1,240 @@ +import config from "config" +import gql from "lib/gql" +import { runAuthenticatedQuery } from "schema/v2/test/utils" + +describe("updateViewingRoomMutation", () => { + const mockUpdateViewingRoomLoader = jest.fn() + + beforeAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = true + }) + + afterAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = false + }) + + describe("success", () => { + const context = { + updateViewingRoomLoader: mockUpdateViewingRoomLoader, + } + + const viewingRoomData = { + id: "viewing-room-id", + } + + beforeEach(() => { + mockUpdateViewingRoomLoader.mockResolvedValue( + Promise.resolve(viewingRoomData) + ) + }) + + afterEach(() => { + mockUpdateViewingRoomLoader.mockReset() + }) + + describe("when passing all possible attributes", () => { + const mutation = gql` + mutation { + updateViewingRoom( + input: { + viewingRoomID: "viewing-room-id" + image: { internalID: "image-id" } + attributes: { + body: "test body" + endAt: "2092-05-23T00:00:00.000Z" + introStatement: "intro statement" + pullQuote: "pull quote" + startAt: "1992-05-23T00:00:00.000Z" + timeZone: "Etc/UTC" + title: "test title" + } + } + ) { + viewingRoomOrErrors { + __typename + + ... on ViewingRoom { + __typename + + internalID + } + + ... on Errors { + errors { + message + } + } + } + } + } + ` + + it("correctly calls the updateViewingRoomLoader", async () => { + const result = await runAuthenticatedQuery(mutation, context) + + expect(mockUpdateViewingRoomLoader).toHaveBeenCalledWith( + "viewing-room-id", + { + ar_image_id: "image-id", + body: "test body", + end_at: "2092-05-23T00:00:00.000Z", + intro_statement: "intro statement", + pull_quote: "pull quote", + start_at: "1992-05-23T00:00:00.000Z", + time_zone: "Etc/UTC", + title: "test title", + } + ) + + expect(result).toMatchInlineSnapshot(` + { + "updateViewingRoom": { + "viewingRoomOrErrors": { + "__typename": "ViewingRoom", + "internalID": "viewing-room-id", + }, + }, + } + `) + }) + }) + + describe("with null and ommited values", () => { + const mutation = gql` + mutation { + updateViewingRoom( + input: { + viewingRoomID: "viewing-room-id" + image: { internalID: "image-id" } + attributes: { + body: null + pullQuote: null + endAt: "2092-05-23T00:00:00.000Z" + startAt: "1992-05-23T00:00:00.000Z" + timeZone: "Etc/UTC" + title: "test title" + } + } + ) { + viewingRoomOrErrors { + __typename + + ... on ViewingRoom { + __typename + + internalID + } + + ... on Errors { + errors { + message + } + } + } + } + } + ` + + it("correctly calls the updateViewingRoomLoader", async () => { + const result = await runAuthenticatedQuery(mutation, context) + + expect(mockUpdateViewingRoomLoader).toHaveBeenCalledWith( + "viewing-room-id", + { + ar_image_id: "image-id", + body: null, + pull_quote: null, + end_at: "2092-05-23T00:00:00.000Z", + start_at: "1992-05-23T00:00:00.000Z", + time_zone: "Etc/UTC", + title: "test title", + } + ) + + expect(result).toMatchInlineSnapshot(` + { + "updateViewingRoom": { + "viewingRoomOrErrors": { + "__typename": "ViewingRoom", + "internalID": "viewing-room-id", + }, + }, + } + `) + }) + }) + }) + + describe("when gravity returns an error", () => { + const context = { + updateViewingRoomLoader: mockUpdateViewingRoomLoader, + } + + beforeEach(() => { + mockUpdateViewingRoomLoader.mockRejectedValue({ + body: { + message: "An error occurred", + }, + }) + }) + + afterEach(() => { + mockUpdateViewingRoomLoader.mockReset() + }) + + const mutation = gql` + mutation { + updateViewingRoom( + input: { + viewingRoomID: "viewing-room-id" + image: { internalID: "image-id" } + attributes: { + body: "test body" + endAt: "2092-05-23T00:00:00.000Z" + introStatement: "intro statement" + pullQuote: "pull quote" + startAt: "1992-05-23T00:00:00.000Z" + timeZone: "Etc/UTC" + title: "test title" + } + } + ) { + viewingRoomOrErrors { + __typename + + ... on ViewingRoom { + __typename + + internalID + } + + ... on Errors { + errors { + message + } + } + } + } + } + ` + + it("returns an error", async () => { + const result = await runAuthenticatedQuery(mutation, context) + + expect(result).toMatchInlineSnapshot(` + { + "updateViewingRoom": { + "viewingRoomOrErrors": { + "__typename": "Errors", + "errors": [ + { + "message": "An error occurred", + }, + ], + }, + }, + } + `) + }) + }) +}) diff --git a/src/schema/v2/viewingRooms/mutations/__tests__/updateViewingRoomSubsectionsMutation.test.ts b/src/schema/v2/viewingRooms/mutations/__tests__/updateViewingRoomSubsectionsMutation.test.ts new file mode 100644 index 0000000000..b93710903b --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/__tests__/updateViewingRoomSubsectionsMutation.test.ts @@ -0,0 +1,135 @@ +import config from "config" +import gql from "lib/gql" +import { runAuthenticatedQuery } from "schema/v2/test/utils" + +describe("updateViewingRoomSubsectionsMutation", () => { + const mockUpdateViewingRoomSubsectionsLoader = jest.fn() + + const context = { + updateViewingRoomSubsectionsLoader: mockUpdateViewingRoomSubsectionsLoader, + } + + beforeAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = true + }) + + afterAll(() => { + config.USE_UNSTITCHED_VIEWING_ROOM_SCHEMA = false + }) + + beforeEach(() => { + mockUpdateViewingRoomSubsectionsLoader.mockResolvedValue( + Promise.resolve([ + { + body: "subsection body", + caption: "subsection caption", + image: { + id: "example-image", + original_height: 100, + original_width: 100, + image_urls: { + normalized: "https://example.com/image.jpg", + }, + }, + image_url: "https://example.com/image.jpg", + id: "example-subsection", + title: "subsection title", + }, + ]) + ) + }) + + afterEach(() => { + mockUpdateViewingRoomSubsectionsLoader.mockReset() + }) + + const mutation = gql` + mutation { + updateViewingRoomSubsections( + input: { + viewingRoomID: "viewing-room-id" + subsections: [ + { + attributes: { + body: "subsection body" + caption: "subsection caption" + title: "subsection title" + } + image: { internalID: "example-image" } + } + { internalID: "subsection-to-delete-id", delete: true } + ] + } + ) { + __typename + + subsections { + __typename + + body + caption + image { + __typename + width + height + internalID + imageURLs { + normalized + } + } + imageURL + internalID + title + } + } + } + ` + + it("correctly calls the updateViewingRoomSubsectionsLoader", async () => { + const result = await runAuthenticatedQuery(mutation, context) + + expect(mockUpdateViewingRoomSubsectionsLoader).toHaveBeenCalledWith( + "viewing-room-id", + { + subsections: [ + { + ar_image_id: "example-image", + attributes: { + body: "subsection body", + caption: "subsection caption", + title: "subsection title", + }, + }, + { attributes: {}, delete: true, id: "subsection-to-delete-id" }, + ], + } + ) + + expect(result).toMatchInlineSnapshot(` + { + "updateViewingRoomSubsections": { + "__typename": "updateViewingRoomSubsectionsPayload", + "subsections": [ + { + "__typename": "ViewingRoomSubsection", + "body": "subsection body", + "caption": "subsection caption", + "image": { + "__typename": "ARImage", + "height": 100, + "imageURLs": { + "normalized": "https://example.com/image.jpg", + }, + "internalID": "example-image", + "width": 100, + }, + "imageURL": "https://example.com/image.jpg", + "internalID": "example-subsection", + "title": "subsection title", + }, + ], + }, + } + `) + }) +}) diff --git a/src/schema/v2/viewingRooms/mutations/createViewingRoomMutation.ts b/src/schema/v2/viewingRooms/mutations/createViewingRoomMutation.ts index e028136728..fdbb628d93 100644 --- a/src/schema/v2/viewingRooms/mutations/createViewingRoomMutation.ts +++ b/src/schema/v2/viewingRooms/mutations/createViewingRoomMutation.ts @@ -1,29 +1,10 @@ -import { - GraphQLID, - GraphQLInputObjectType, - GraphQLNonNull, - GraphQLString, - GraphQLUnionType, -} from "graphql" +import { GraphQLString } from "graphql" import { mutationWithClientMutationId } from "graphql-relay" -import { ErrorsType } from "lib/gravityErrorHandler" import { identity, pickBy } from "lodash" -import { ViewingRoomType } from "schema/v2/viewingRoom" import { ResolverContext } from "types/graphql" - -const ResponseOrErrorType = new GraphQLNonNull( - new GraphQLUnionType({ - name: "ViewingRoomOrErrorsUnion", - types: [ViewingRoomType, ErrorsType], - resolveType: (data) => { - if (data.id) { - return ViewingRoomType - } - - return ErrorsType - }, - }) -) +import { ViewingRoomInputAttributesType } from "./viewingRoomInputAttributes" +import { ViewingRoomOrErrorType } from "./viewingRoomOrError" +import { ARImageInputType } from "./ARImageInput" export const createViewingRoomMutation = mutationWithClientMutationId< any, @@ -36,37 +17,7 @@ export const createViewingRoomMutation = mutationWithClientMutationId< // This is because Gravity has such duplication https://github.com/artsy/gravity/blob/main/app/graphql/mutations/create_viewing_room.rb#L12 // We can get rid of it once we finish with the migration. For now I want to keep such changes to a minimum attributes: { - type: new GraphQLInputObjectType({ - name: "ViewingRoomAttributes", - fields: { - body: { - type: GraphQLString, - }, - endAt: { - type: GraphQLString, - description: "Datetime (in UTC) when Viewing Room closes", - }, - introStatement: { - type: GraphQLString, - }, - pullQuote: { - type: GraphQLString, - }, - startAt: { - type: GraphQLString, - description: "Datetime (in UTC) when Viewing Room opens", - }, - timeZone: { - type: GraphQLString, - description: - "Time zone (tz database format, e.g. America/New_York) in which start_at/end_at attributes were input", - }, - title: { - type: GraphQLString, - description: "Title", - }, - }, - }), + type: ViewingRoomInputAttributesType, }, body: { type: GraphQLString, @@ -77,14 +28,7 @@ export const createViewingRoomMutation = mutationWithClientMutationId< description: "End datetime", }, image: { - type: new GraphQLInputObjectType({ - name: "ARImageInput", - fields: { - internalID: { - type: new GraphQLNonNull(GraphQLID), - }, - }, - }), + type: ARImageInputType, }, introStatement: { type: GraphQLString, @@ -116,7 +60,7 @@ export const createViewingRoomMutation = mutationWithClientMutationId< }, outputFields: { viewingRoomOrErrors: { - type: ResponseOrErrorType, + type: ViewingRoomOrErrorType, resolve: (result) => result, }, }, diff --git a/src/schema/v2/viewingRooms/mutations/deleteViewingRoomMutation.ts b/src/schema/v2/viewingRooms/mutations/deleteViewingRoomMutation.ts new file mode 100644 index 0000000000..b33fdf6585 --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/deleteViewingRoomMutation.ts @@ -0,0 +1,25 @@ +import { GraphQLNonNull, GraphQLString } from "graphql" +import { mutationWithClientMutationId } from "graphql-relay" +import { ResolverContext } from "types/graphql" + +export const deleteViewingRoomMutation = mutationWithClientMutationId< + any, + any, + ResolverContext +>({ + name: "deleteViewingRoom", + inputFields: { + viewingRoomID: { + type: new GraphQLNonNull(GraphQLString), + }, + }, + mutateAndGetPayload: async (args, { deleteViewingRoomLoader }) => { + if (!deleteViewingRoomLoader) { + throw new Error("You need to be signed in to perform this action") + } + + const response = await deleteViewingRoomLoader(args.viewingRoomID) + + return response + }, +}) diff --git a/src/schema/v2/viewingRooms/mutations/publishViewingRoomMutation.ts b/src/schema/v2/viewingRooms/mutations/publishViewingRoomMutation.ts new file mode 100644 index 0000000000..fa29f5e0aa --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/publishViewingRoomMutation.ts @@ -0,0 +1,27 @@ +import { GraphQLNonNull, GraphQLString } from "graphql" +import { mutationWithClientMutationId } from "graphql-relay" +import { ResolverContext } from "types/graphql" + +export const publishViewingRoomMutation = mutationWithClientMutationId< + any, + any, + ResolverContext +>({ + name: "publishViewingRoom", + inputFields: { + viewingRoomID: { + type: new GraphQLNonNull(GraphQLString), + }, + }, + mutateAndGetPayload: async (args, { updateViewingRoomLoader }) => { + if (!updateViewingRoomLoader) { + throw new Error("You need to be signed in to perform this action") + } + + const response = await updateViewingRoomLoader(args.viewingRoomID, { + published: true, + }) + + return response + }, +}) diff --git a/src/schema/v2/viewingRooms/mutations/unpublishViewingRoomMutation.ts b/src/schema/v2/viewingRooms/mutations/unpublishViewingRoomMutation.ts new file mode 100644 index 0000000000..c6369e35b7 --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/unpublishViewingRoomMutation.ts @@ -0,0 +1,27 @@ +import { GraphQLNonNull, GraphQLString } from "graphql" +import { mutationWithClientMutationId } from "graphql-relay" +import { ResolverContext } from "types/graphql" + +export const unpublishViewingRoomMutation = mutationWithClientMutationId< + any, + any, + ResolverContext +>({ + name: "unpublishViewingRoom", + inputFields: { + viewingRoomID: { + type: new GraphQLNonNull(GraphQLString), + }, + }, + mutateAndGetPayload: async (args, { updateViewingRoomLoader }) => { + if (!updateViewingRoomLoader) { + throw new Error("You need to be signed in to perform this action") + } + + const response = await updateViewingRoomLoader(args.viewingRoomID, { + published: false, + }) + + return response + }, +}) diff --git a/src/schema/v2/viewingRooms/mutations/updateViewingRoomArtworks.ts b/src/schema/v2/viewingRooms/mutations/updateViewingRoomArtworks.ts new file mode 100644 index 0000000000..5a2c897f01 --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/updateViewingRoomArtworks.ts @@ -0,0 +1,69 @@ +import { + GraphQLBoolean, + GraphQLID, + GraphQLInputObjectType, + GraphQLInt, + GraphQLList, + GraphQLNonNull, + GraphQLString, +} from "graphql" +import { mutationWithClientMutationId } from "graphql-relay" +import { snakeCaseKeys } from "lib/helpers" +import { ResolverContext } from "types/graphql" + +const ViewingRoomArtworkInput = new GraphQLInputObjectType({ + name: "ViewingRoomArtworkInput", + fields: { + artworkID: { + type: new GraphQLNonNull(GraphQLID), + }, + position: { + type: GraphQLInt, + }, + delete: { + type: GraphQLBoolean, + }, + internalID: { + type: GraphQLID, + }, + }, +}) + +export const updateViewingRoomArtworksMutation = mutationWithClientMutationId< + any, + any, + ResolverContext +>({ + name: "updateViewingRoomArtworks", + inputFields: { + viewingRoomID: { + type: new GraphQLNonNull(GraphQLString), + }, + artworks: { + type: new GraphQLNonNull(new GraphQLList(ViewingRoomArtworkInput)), + }, + }, + outputFields: { + artworkIDs: { + type: new GraphQLNonNull( + new GraphQLList(new GraphQLNonNull(GraphQLString)) + ), + resolve: ({ artwork_ids }) => artwork_ids, + }, + }, + mutateAndGetPayload: async (args, { updateViewingRoomArtworksLoader }) => { + if (!updateViewingRoomArtworksLoader) { + throw new Error("You need to be signed in to perform this action") + } + + const artworks = args.artworks.map((artwork, _index) => + snakeCaseKeys(artwork) + ) + + const response = await updateViewingRoomArtworksLoader(args.viewingRoomID, { + artworks: artworks, + }) + + return response + }, +}) diff --git a/src/schema/v2/viewingRooms/mutations/updateViewingRoomMutation.ts b/src/schema/v2/viewingRooms/mutations/updateViewingRoomMutation.ts new file mode 100644 index 0000000000..bdce04cedc --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/updateViewingRoomMutation.ts @@ -0,0 +1,67 @@ +import { GraphQLNonNull, GraphQLString } from "graphql" +import { mutationWithClientMutationId } from "graphql-relay" +import { ResolverContext } from "types/graphql" +import { ViewingRoomInputAttributesType } from "./viewingRoomInputAttributes" +import { ViewingRoomOrErrorType } from "./viewingRoomOrError" +import { ARImageInputType } from "./ARImageInput" + +export const updateViewingRoomMutation = mutationWithClientMutationId< + any, + any, + ResolverContext +>({ + name: "updateViewingRoom", + inputFields: { + attributes: { + type: ViewingRoomInputAttributesType, + }, + image: { + type: ARImageInputType, + }, + viewingRoomID: { + type: new GraphQLNonNull(GraphQLString), + }, + }, + outputFields: { + viewingRoomOrErrors: { + type: ViewingRoomOrErrorType, + resolve: (result) => result, + }, + }, + mutateAndGetPayload: async (args, { updateViewingRoomLoader }) => { + if (!updateViewingRoomLoader) { + throw new Error("You need to be signed in to perform this action") + } + + try { + const gravityArgs = { + ar_image_id: args.image?.internalID, + body: args.attributes?.body, + end_at: args.attributes?.endAt, + intro_statement: args.attributes?.introStatement, + pull_quote: args.attributes?.pullQuote, + start_at: args.attributes?.startAt, + time_zone: args.attributes?.timeZone, + title: args.attributes?.title, + } + + const response = await updateViewingRoomLoader( + args.viewingRoomID, + gravityArgs + ) + + return response + } catch (error) { + const { body } = error + + return { + errors: [ + { + message: body.message ?? body.error, + code: "invalid", + }, + ], + } + } + }, +}) diff --git a/src/schema/v2/viewingRooms/mutations/updateViewingRoomSubsections.ts b/src/schema/v2/viewingRooms/mutations/updateViewingRoomSubsections.ts new file mode 100644 index 0000000000..3526d3a43a --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/updateViewingRoomSubsections.ts @@ -0,0 +1,104 @@ +import { + GraphQLBoolean, + GraphQLID, + GraphQLInputObjectType, + GraphQLList, + GraphQLNonNull, + GraphQLString, +} from "graphql" +import { mutationWithClientMutationId } from "graphql-relay" +import { ResolverContext } from "types/graphql" +import { ARImageInputType } from "./ARImageInput" +import { ViewingRoomSubsectionType } from "schema/v2/viewingRoomSubsection" +import { identity, pickBy } from "lodash" + +const ViewingRoomSubsectionAttributes = new GraphQLInputObjectType({ + name: "ViewingRoomSubsectionAttributes", + fields: { + body: { + type: GraphQLString, + }, + caption: { + type: GraphQLString, + }, + title: { + type: GraphQLString, + }, + }, +}) + +const ViewingRoomSubsectionInput = new GraphQLInputObjectType({ + name: "ViewingRoomSubsectionInput", + fields: { + attributes: { + type: ViewingRoomSubsectionAttributes, + }, + delete: { + type: GraphQLBoolean, + defaultValue: false, + }, + image: { + type: ARImageInputType, + }, + internalID: { + type: GraphQLID, + }, + }, +}) + +export const updateViewingRoomSubsectionsMutation = mutationWithClientMutationId< + any, + any, + ResolverContext +>({ + name: "updateViewingRoomSubsections", + inputFields: { + viewingRoomID: { + type: new GraphQLNonNull(GraphQLString), + }, + subsections: { + type: new GraphQLNonNull(new GraphQLList(ViewingRoomSubsectionInput)), + }, + }, + outputFields: { + subsections: { + type: new GraphQLNonNull( + new GraphQLList(new GraphQLNonNull(ViewingRoomSubsectionType)) + ), + resolve: (result) => result, + }, + }, + mutateAndGetPayload: async (args, { updateViewingRoomSubsectionsLoader }) => { + if (!updateViewingRoomSubsectionsLoader) { + throw new Error("You need to be signed in to perform this action") + } + + const subsections = args.subsections.map((subsection, _index) => { + return pickBy( + { + id: subsection.internalID, + delete: subsection.delete, + ar_image_id: subsection.image?.internalID, + attributes: pickBy( + { + body: subsection.attributes?.body, + caption: subsection.attributes?.caption, + title: subsection.attributes?.title, + }, + identity + ), + }, + identity + ) + }) + + const response = await updateViewingRoomSubsectionsLoader( + args.viewingRoomID, + { + subsections: subsections, + } + ) + + return response + }, +}) diff --git a/src/schema/v2/viewingRooms/mutations/viewingRoomInputAttributes.ts b/src/schema/v2/viewingRooms/mutations/viewingRoomInputAttributes.ts new file mode 100644 index 0000000000..88adf81687 --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/viewingRoomInputAttributes.ts @@ -0,0 +1,33 @@ +import { GraphQLInputObjectType, GraphQLString } from "graphql" + +export const ViewingRoomInputAttributesType = new GraphQLInputObjectType({ + name: "ViewingRoomAttributes", + fields: { + body: { + type: GraphQLString, + }, + endAt: { + type: GraphQLString, + description: "Datetime (in UTC) when Viewing Room closes", + }, + introStatement: { + type: GraphQLString, + }, + pullQuote: { + type: GraphQLString, + }, + startAt: { + type: GraphQLString, + description: "Datetime (in UTC) when Viewing Room opens", + }, + timeZone: { + type: GraphQLString, + description: + "Time zone (tz database format, e.g. America/New_York) in which start_at/end_at attributes were input", + }, + title: { + type: GraphQLString, + description: "Title", + }, + }, +}) diff --git a/src/schema/v2/viewingRooms/mutations/viewingRoomOrError.ts b/src/schema/v2/viewingRooms/mutations/viewingRoomOrError.ts new file mode 100644 index 0000000000..2409f891bc --- /dev/null +++ b/src/schema/v2/viewingRooms/mutations/viewingRoomOrError.ts @@ -0,0 +1,17 @@ +import { GraphQLNonNull, GraphQLUnionType } from "graphql" +import { ErrorsType } from "lib/gravityErrorHandler" +import { ViewingRoomType } from "schema/v2/viewingRoom" + +export const ViewingRoomOrErrorType = new GraphQLNonNull( + new GraphQLUnionType({ + name: "ViewingRoomOrErrorsUnion", + types: [ViewingRoomType, ErrorsType], + resolveType: (data) => { + if (data.id) { + return ViewingRoomType + } + + return ErrorsType + }, + }) +)