From 35d35885ec74233fad224795da58c2547198078a Mon Sep 17 00:00:00 2001 From: Mathijs Miermans Date: Mon, 20 May 2024 14:28:40 -0700 Subject: [PATCH] feat: [MC-1035] Pass through enableRankByRegion (#75) * fix: openapi-typescript failed to run TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension .mts for scripts/generateOpenAPITypes.mts * feat: pass enableRankingByRegion to recommendation-api * fix: pass enableRankingByRegion to query --- .eslintrc.js | 1 + openapi.yml | 7 + package-lock.json | 81 +++--- package.json | 4 +- scripts/README.md | 12 - scripts/generateOpenAPITypes.mts | 24 -- scripts/tsconfig.openapi-typescript.json | 11 - .../desktop/recommendations/inputs.spec.ts | 45 +++ src/api/desktop/recommendations/inputs.ts | 6 +- src/generated/graphql/types.ts | 257 +++++++++++++++++- src/generated/openapi/types.ts | 104 +++---- .../recommendations/Recommendations.graphql | 4 +- 12 files changed, 413 insertions(+), 143 deletions(-) delete mode 100644 scripts/generateOpenAPITypes.mts delete mode 100644 scripts/tsconfig.openapi-typescript.json diff --git a/.eslintrc.js b/.eslintrc.js index cee7c5b82..49969e0d7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,4 @@ module.exports = { extends: ['@pocket-tools/eslint-config'], + ignorePatterns: ['src/generated/**'], }; diff --git a/openapi.yml b/openapi.yml index e1a7f9db1..136c25a2b 100644 --- a/openapi.yml +++ b/openapi.yml @@ -309,6 +309,13 @@ paths: description: This region string is Fx domain language, and built from Fx expectations. Parameter values are not case sensitive. See [Firefox Home & New Tab Regional Differences](https://mozilla-hub.atlassian.net/wiki/spaces/FPS/pages/80448805/Regional+Differences). schema: type: string + - name: enableRankingByRegion + in: query + required: false + description: Returns recommendations specific to the region if set to 1. + schema: + type: integer + enum: [0, 1] responses: '200': description: OK diff --git a/package-lock.json b/package-lock.json index 9cb6dac08..912ddbd75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "jest": "29.7.0", "nock": "13.2.4", "nodemon": "2.0.20", - "openapi-typescript": "^6.0.3", + "openapi-typescript": "^6.7.5", "supertest": "6.3.3", "ts-jest": "29.1.2", "ts-node": "^10.9.2" @@ -1392,6 +1392,15 @@ "npm": ">=6.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@graphql-codegen/add": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-4.0.1.tgz", @@ -7694,9 +7703,9 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -14276,16 +14285,16 @@ } }, "node_modules/openapi-typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.0.3.tgz", - "integrity": "sha512-zEIqeBdpLRNmfPJX6GG4ntnOesDPvzZ7VM+1P6/PzBUP6KVSgAaz32Ly1baMydVzyvBLVcJBHYDY8WRwasiIyg==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.7.5.tgz", + "integrity": "sha512-ZD6dgSZi0u1QCP55g8/2yS5hNJfIpgqsSGHLxxdOjvY7eIrXzj271FJEQw33VwsZ6RCtO/NOuhxa7GBWmEudyA==", "dev": true, "dependencies": { "ansi-colors": "^4.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.2", "js-yaml": "^4.1.0", - "supports-color": "^9.2.3", - "undici": "^5.12.0", + "supports-color": "^9.4.0", + "undici": "^5.28.2", "yargs-parser": "^21.1.1" }, "bin": { @@ -14293,9 +14302,9 @@ } }, "node_modules/openapi-typescript/node_modules/supports-color": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", - "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", "dev": true, "engines": { "node": ">=12" @@ -17623,12 +17632,12 @@ "dev": true }, "node_modules/undici": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", - "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, "dependencies": { - "busboy": "^1.6.0" + "@fastify/busboy": "^2.0.0" }, "engines": { "node": ">=14.0" @@ -19166,6 +19175,12 @@ "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", "dev": true }, + "@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true + }, "@graphql-codegen/add": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-4.0.1.tgz", @@ -24028,9 +24043,9 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" }, "fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -28638,23 +28653,23 @@ } }, "openapi-typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.0.3.tgz", - "integrity": "sha512-zEIqeBdpLRNmfPJX6GG4ntnOesDPvzZ7VM+1P6/PzBUP6KVSgAaz32Ly1baMydVzyvBLVcJBHYDY8WRwasiIyg==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.7.5.tgz", + "integrity": "sha512-ZD6dgSZi0u1QCP55g8/2yS5hNJfIpgqsSGHLxxdOjvY7eIrXzj271FJEQw33VwsZ6RCtO/NOuhxa7GBWmEudyA==", "dev": true, "requires": { "ansi-colors": "^4.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.2", "js-yaml": "^4.1.0", - "supports-color": "^9.2.3", - "undici": "^5.12.0", + "supports-color": "^9.4.0", + "undici": "^5.28.2", "yargs-parser": "^21.1.1" }, "dependencies": { "supports-color": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", - "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", "dev": true }, "yargs-parser": { @@ -31059,12 +31074,12 @@ "dev": true }, "undici": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", - "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, "requires": { - "busboy": "^1.6.0" + "@fastify/busboy": "^2.0.0" } }, "undici-types": { diff --git a/package.json b/package.json index 4eb8d0399..9e0eba07b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "rm -rf dist && tsc", "codegen": "npm run codegen:graphql-types", "codegen:graphql-types": "graphql-codegen", - "codegen:openapi-types": "npm run lint-openapi && ts-node-esm -P scripts/tsconfig.openapi-typescript.json scripts/generateOpenAPITypes.mts", + "codegen:openapi-types": "openapi-typescript ./openapi.yml -o ./src/generated/openapi/types.ts", "docs": "redocly preview-docs openapi.yml", "watch": "tsc -w & nodemon", "start": "node dist/main.js", @@ -68,7 +68,7 @@ "jest": "29.7.0", "nock": "13.2.4", "nodemon": "2.0.20", - "openapi-typescript": "^6.0.3", + "openapi-typescript": "^6.7.5", "supertest": "6.3.3", "ts-jest": "29.1.2", "ts-node": "^10.9.2" diff --git a/scripts/README.md b/scripts/README.md index 5b9a69299..00d65daf0 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -3,15 +3,3 @@ These files are scripts intended to be run during local development, or during CI/CD. These are not transpiled into `dist`, and cannot be executed in production environments. - -## generateOpenAPITypes.mts - -To execute this script: - -```bash -npm run codegen -``` - -This script generates `src/generated/openapi/types.ts`. This requires a separate tsconfig that permits the usage of ESModules, which lives in `tsconfig.openapi-typescript.json`. - -This script does not validate `openapi.yml`. It is recommended to lint that file after changing its contents. diff --git a/scripts/generateOpenAPITypes.mts b/scripts/generateOpenAPITypes.mts deleted file mode 100644 index 5db2c45b4..000000000 --- a/scripts/generateOpenAPITypes.mts +++ /dev/null @@ -1,24 +0,0 @@ -import * as fs from 'fs'; -import openapiTS, { OpenAPITSOptions } from 'openapi-typescript'; -import { URL } from 'node:url'; - -const OPTIONS: OpenAPITSOptions = { - // generated types do not conform to ts/lint rules, disable them for these files - commentHeader: `// THIS FILE IS GENERATED, DO NOT EDIT! -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -/* eslint-disable prettier/prettier */ -/* tslint:disable */ -/* eslint:disable */ -`, -}; - -const main = async () => { - const localPath = new URL('../openapi.yml', import.meta.url); - const output = await openapiTS(localPath, OPTIONS); - fs.writeFileSync('./src/generated/openapi/types.ts', output); -}; - -(async () => { - await main(); -})(); diff --git a/scripts/tsconfig.openapi-typescript.json b/scripts/tsconfig.openapi-typescript.json deleted file mode 100644 index 35837d58c..000000000 --- a/scripts/tsconfig.openapi-typescript.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "moduleResolution": "node" - }, - "ts-node": { - "compilerOptions": { - "module": "esnext", - "target": "ESNext" - } - } -} diff --git a/src/api/desktop/recommendations/inputs.spec.ts b/src/api/desktop/recommendations/inputs.spec.ts index 8f35985a6..d81ef78b9 100644 --- a/src/api/desktop/recommendations/inputs.spec.ts +++ b/src/api/desktop/recommendations/inputs.spec.ts @@ -177,6 +177,12 @@ describe('input.ts recommendations query parameters', () => { }); }); + it('sets enableRankingByRegion to false if no default is provided', () => { + const res = setDefaultsAndCoerceTypes({}); + // validation should return an error in this case, validating defaults though + expect(res.enableRankingByRegion).toStrictEqual(false); + }); + describe('handleQueryParameters', () => { it('returns errors if invalid query parameters', () => { const params: RecommendationsQueryParameterStrings = { @@ -198,6 +204,44 @@ describe('input.ts recommendations query parameters', () => { ); }); + it('returns enableRankingByRegion as true when set to 1', () => { + const params: RecommendationsQueryParameterStrings = { + count: faker.datatype.number({ min: 1, max: 30 }).toString(), + locale: 'fr', + region: 'FR', + enableRankingByRegion: '1', + }; + + const variables = handleQueryParameters(params); + expect(variables).toStrictEqual( + expect.objectContaining({ + count: parseInt(params.count, 10), + locale: params.locale, + region: params.region, + enableRankingByRegion: true, + }) + ); + }); + + it('returns enableRankingByRegion as false when set to 0', () => { + const params: RecommendationsQueryParameterStrings = { + count: faker.datatype.number({ min: 1, max: 30 }).toString(), + locale: 'fr', + region: 'FR', + enableRankingByRegion: '0', + }; + + const variables = handleQueryParameters(params); + expect(variables).toStrictEqual( + expect.objectContaining({ + count: parseInt(params.count, 10), + locale: params.locale, + region: params.region, + enableRankingByRegion: false, + }) + ); + }); + it('returns GraphQL query variables on success', () => { const params: RecommendationsQueryParameterStrings = { count: faker.datatype.number({ min: 1, max: 30 }).toString(), @@ -211,6 +255,7 @@ describe('input.ts recommendations query parameters', () => { count: parseInt(params.count, 10), locale: params.locale, region: params.region, + enableRankingByRegion: false, }) ); }); diff --git a/src/api/desktop/recommendations/inputs.ts b/src/api/desktop/recommendations/inputs.ts index 1503fb5e3..56193c33c 100644 --- a/src/api/desktop/recommendations/inputs.ts +++ b/src/api/desktop/recommendations/inputs.ts @@ -25,6 +25,7 @@ export type RecommendationsQueryParameterStrings = Partial< * This type captures that uncertainty. */ type PreValidatedQueryParameters = { + enableRankingByRegion?: boolean; count: number; locale?: string; region?: string; @@ -120,6 +121,7 @@ export const setDefaultsAndCoerceTypes = ( return { ...withDefaults, count: parseInt(withDefaults.count, 10), + enableRankingByRegion: withDefaults.enableRankingByRegion == '1', }; }; @@ -132,7 +134,7 @@ export const setDefaultsAndCoerceTypes = ( */ export const validate = ( query: PreValidatedQueryParameters -): BFFFxErrorInstanceType | RecommendationsQueryParameters => { +): BFFFxErrorInstanceType | PreValidatedQueryParameters => { // errorDetails is empty if all fields are valid const errorDetails = [ isValidCount(query.count), @@ -160,7 +162,7 @@ export const validate = ( return error; } - return query as RecommendationsQueryParameters; + return query as PreValidatedQueryParameters; }; /** diff --git a/src/generated/graphql/types.ts b/src/generated/graphql/types.ts index a06b2972a..ced237297 100644 --- a/src/generated/graphql/types.ts +++ b/src/generated/graphql/types.ts @@ -21,9 +21,11 @@ export type Scalars = { FunctionalBoostValue: any; ISOString: any; Markdown: any; + Max300CharString: any; NonNegativeInt: any; Timestamp: any; Url: any; + ValidUrl: any; }; /** @@ -387,6 +389,38 @@ export type CorpusSlateLineupSlatesArgs = { */ export type CorpusTarget = Collection | SyndicatedArticle; +/** Input for creating a new User-highlighted passage on a SavedItem. */ +export type CreateHighlightByUrlInput = { + /** + * Optionally, a client-generated UUID to identify the highlight. + * If one is not passed, it will be created. Must be in UUID format, + * or will fail generation. Will not overwrite existing data if there + * is an ID collision. + */ + id?: InputMaybe; + /** Optional note generated by User */ + note?: InputMaybe; + /** + * Patch string generated by 'DiffMatchPatch' library, serialized + * into text via `patch_toText` method. + * Format is similar to UniDiff but is character-based. + * The patched text depends on version. For example, the version 2 + * patch surrounds the highlighted text portion with a pair of + * sentinel tags: '' + * Reference: https://github.com/google/diff-match-patch + */ + patch: Scalars['String']; + /** + * The full text of the highlighted passage. Used as a fallback for + * rendering highlight if the patch fails. + */ + quote: Scalars['String']; + /** The url of the Item that should be annotated in the User's list */ + url: Scalars['ValidUrl']; + /** Annotation data version */ + version: Scalars['Int']; +}; + /** Input for creating a new User-highlighted passage on a SavedItem. */ export type CreateHighlightInput = { /** @@ -714,7 +748,9 @@ export type Item = { */ originDomainId?: Maybe; /** The client preview/display logic for this url */ - preview?: Maybe; + preview?: Maybe; + /** A server generated unique reader slug for this item based on itemId */ + readerSlug: Scalars['String']; /** Recommend similar articles to show in the bottom of an article. */ relatedAfterArticle: Array; /** Recommend similar articles after saving. */ @@ -806,7 +842,7 @@ export type ItemNotFound = { /** Union type for items that may or may not be processed */ export type ItemResult = Item | PendingItem; -export type ItemSummary = { +export type ItemSummary = PocketMetadata & { __typename?: 'ItemSummary'; authors?: Maybe>; datePublished?: Maybe; @@ -815,6 +851,7 @@ export type ItemSummary = { id: Scalars['ID']; image?: Maybe; item?: Maybe; + source: PocketMetadataSource; title?: Maybe; url: Scalars['Url']; }; @@ -935,6 +972,20 @@ export type MarticleText = { /** Default Mutation Type */ export type Mutation = { __typename?: 'Mutation'; + /** + * Attach share context to a Pocket Share. If a context already exists + * on the Pocket Share, it will be overrwritten. Session ID via the `guid` + * field on the JWT is used to determine ownership of a share. + * That means users may only edit share links created in the same + * session (intended to be a post-share add, not something returned to + * later). It also lets us attribute ownership to anonymous/logged-out + * users. + * Null values in provided context will not overrwrite existing values + * if there are any, but but empty values will (e.g. empty string, empty array). + * Attempting to update a nonexistent share or a share that is not owned + * by the session user will return ShareNotFound. + */ + addShareContext?: Maybe; /** Add a batch of items to an existing shareable list. */ addToShareableList: ShareableList; /** @@ -946,6 +997,8 @@ export type Mutation = { clearTags?: Maybe; /** Add a batch of items to an existing shareable list. */ createAndAddToShareableList?: Maybe; + /** Create new highlight annotation(s). Returns the data for the created Highlight object. */ + createHighlightByUrl: Highlight; /** Create new highlight note. Returns the data for the created Highlight note. */ createSavedItemHighlightNote?: Maybe; /** Create new highlight annotation(s). Returns the data for the created Highlight object(s). */ @@ -956,6 +1009,11 @@ export type Mutation = { * Returns the list of `SavedItem` for which the tags were added */ createSavedItemTags: Array; + /** + * Create a Pocket Share for a provided target URL, optionally + * with additional share context. + */ + createShareLink?: Maybe; /** * Creates a Shareable List. Takes in an optional listItemData parameter to create a ShareableListItem * along with a ShareableList. @@ -1051,6 +1109,11 @@ export type Mutation = { * Accepts a list of PocketSave Ids that we want to favorite. */ saveFavorite?: Maybe; + /** + * Save search to potentially appear in recentSearches response. + * Requires premium account (otherwise will send ForbiddenError). + */ + saveSearch?: Maybe; /** Unarchives PocketSaves */ saveUnArchive?: Maybe; /** @@ -1178,6 +1241,13 @@ export type Mutation = { }; +/** Default Mutation Type */ +export type MutationAddShareContextArgs = { + context: ShareContextInput; + slug: Scalars['ID']; +}; + + /** Default Mutation Type */ export type MutationAddToShareableListArgs = { items: Array; @@ -1205,6 +1275,12 @@ export type MutationCreateAndAddToShareableListArgs = { }; +/** Default Mutation Type */ +export type MutationCreateHighlightByUrlArgs = { + input: CreateHighlightByUrlInput; +}; + + /** Default Mutation Type */ export type MutationCreateSavedItemHighlightNoteArgs = { id: Scalars['ID']; @@ -1225,6 +1301,13 @@ export type MutationCreateSavedItemTagsArgs = { }; +/** Default Mutation Type */ +export type MutationCreateShareLinkArgs = { + context?: InputMaybe; + target: Scalars['ValidUrl']; +}; + + /** Default Mutation Type */ export type MutationCreateShareableListArgs = { listData: CreateShareableListInput; @@ -1373,6 +1456,12 @@ export type MutationSaveFavoriteArgs = { }; +/** Default Mutation Type */ +export type MutationSaveSearchArgs = { + search: RecentSearchInput; +}; + + /** Default Mutation Type */ export type MutationSaveUnArchiveArgs = { id: Array; @@ -1594,6 +1683,29 @@ export type NumberedListElement = ListElement & { level: Scalars['Int']; }; +export type OEmbed = PocketMetadata & { + __typename?: 'OEmbed'; + authors?: Maybe>; + datePublished?: Maybe; + domain?: Maybe; + excerpt?: Maybe; + htmlEmbed?: Maybe; + id: Scalars['ID']; + image?: Maybe; + item?: Maybe; + source: PocketMetadataSource; + title?: Maybe; + type?: Maybe; + url: Scalars['Url']; +}; + +export enum OEmbedType { + Link = 'LINK', + Photo = 'PHOTO', + Rich = 'RICH', + Video = 'VIDEO' +} + /** Input for offset-pagination (internal backend use only). */ export type OffsetPaginationInput = { /** Defaults to 30 */ @@ -1678,6 +1790,25 @@ export enum PendingItemStatus { Unresolved = 'UNRESOLVED' } +export type PocketMetadata = { + authors?: Maybe>; + datePublished?: Maybe; + domain?: Maybe; + excerpt?: Maybe; + id: Scalars['ID']; + image?: Maybe; + item?: Maybe; + source: PocketMetadataSource; + title?: Maybe; + url: Scalars['Url']; +}; + +export enum PocketMetadataSource { + Oembed = 'OEMBED', + Opengraph = 'OPENGRAPH', + PocketParser = 'POCKET_PARSER' +} + /** * New Pocket Save Type, replacing SavedItem. * @@ -1731,7 +1862,12 @@ export enum PocketSaveStatus { export type PocketShare = { __typename?: 'PocketShare'; - id: Scalars['ID']; + context?: Maybe; + createdAt: Scalars['ISOString']; + preview?: Maybe; + shareUrl: Scalars['ValidUrl']; + slug: Scalars['ID']; + targetUrl: Scalars['ValidUrl']; }; export enum PremiumFeature { @@ -1865,13 +2001,18 @@ export type Query = { * Resolve Reader View links which might point to SavedItems that do not * exist, aren't in the Pocket User's list, or are requested by a logged-out * user (or user without a Pocket Account). - * Fetches data to create an interstitial page/modal so the visitor can click - * through to the shared site. + * Fetches data which clients can use to generate an appropriate fallback view + * that allows users to preview the content and access the original source site. */ readerSlug: ReaderViewResult; /** List all topics that the user can express a preference for. */ recommendationPreferenceTopics: Array; scheduledSurface: ScheduledSurface; + /** + * Resolve data for a Shared link, or return a Not Found + * message if the share does not exist. + */ + shareSlug?: Maybe; /** * Looks up and returns a Shareable List with a given external ID for a given user. * (the user ID will be coming through with the headers) @@ -2006,6 +2147,7 @@ export type QueryItemByUrlArgs = { * TODO: These belong in a seperate User Service that provides a User object (the user settings will probably exist there too) */ export type QueryNewTabSlateArgs = { + enableRankingByRegion?: InputMaybe; locale: Scalars['String']; region?: InputMaybe; }; @@ -2029,6 +2171,15 @@ export type QueryScheduledSurfaceArgs = { }; +/** + * Default root level query type. All authorization checks are done in these queries. + * TODO: These belong in a seperate User Service that provides a User object (the user settings will probably exist there too) + */ +export type QueryShareSlugArgs = { + slug: Scalars['ID']; +}; + + /** * Default root level query type. All authorization checks are done in these queries. * TODO: These belong in a seperate User Service that provides a User object (the user settings will probably exist there too) @@ -2074,13 +2225,28 @@ export type QueryUnleashAssignmentsArgs = { context: UnleashContext; }; +/** + * Metadata of an Item in Pocket for preview purposes, + * or an ItemNotFound result if the record does not exist. + */ export type ReaderFallback = ItemNotFound | ReaderInterstitial; +/** + * Card preview data for Items resolved from reader view + * (getpocket.com/read/) links. + * + * Should be used to create a view if Reader Mode cannot + * be rendered (e.g. the link is visited by an anonymous + * Pocket user, or a Pocket User that does not have the + * underlying Item in their Saves). Due to legal obligations + * we can only display Reader Mode for SavedItems. + */ export type ReaderInterstitial = { __typename?: 'ReaderInterstitial'; - itemCard?: Maybe; + itemCard?: Maybe; }; +/** Result for resolving a getpocket.com/read/ link. */ export type ReaderViewResult = { __typename?: 'ReaderViewResult'; fallbackPage?: Maybe; @@ -2096,6 +2262,29 @@ export type RecItUserProfile = { userModels: Array; }; +export type RecentSearch = { + __typename?: 'RecentSearch'; + context?: Maybe; + sortId: Scalars['Int']; + term: Scalars['String']; +}; + +export type RecentSearchContext = { + __typename?: 'RecentSearchContext'; + key?: Maybe; + value?: Maybe; +}; + +export type RecentSearchInput = { + /** The term that was used for search */ + term: Scalars['String']; + /** + * Optional, the time the search was performed. + * Defaults to current server time at time of request. + */ + timestamp?: InputMaybe; +}; + /** Represents a Recommendation from Pocket */ export type Recommendation = { __typename?: 'Recommendation'; @@ -2477,7 +2666,15 @@ export type SavedItemsFilter = { * To get untagged items, include the string '_untagged_'. */ tagNames?: InputMaybe>; - /** Optional, filter to get SavedItems updated since a unix timestamp */ + /** + * Optional, filter to get SavedItems updated before a unix timestamp. + * Mutually exclusive with `updatedSince` option. + */ + updatedBefore?: InputMaybe; + /** + * Optional, filter to get SavedItems updated since a unix timestamp. + * Mutually exclusive with `updatedBefore` option. + */ updatedSince?: InputMaybe; }; @@ -2692,6 +2889,48 @@ export enum SearchStatus { Queued = 'QUEUED' } +export type ShareContext = { + __typename?: 'ShareContext'; + /** User-provided highlights of the content */ + highlights?: Maybe>; + /** A user-provided comment/note on the shared content. */ + note?: Maybe; +}; + +/** Input for mutation which creates a new Pocket Share link. */ +export type ShareContextInput = { + /** Quoted content from the Share source */ + highlights?: InputMaybe; + /** A note/comment about the Share (up to 500 characters). */ + note?: InputMaybe; +}; + +export type ShareHighlight = { + __typename?: 'ShareHighlight'; + /** Highlighted text on a piece of shared content. */ + quote: Scalars['String']; +}; + +export type ShareHighlightInput = { + /** + * Highlighted text on a piece of shared content. + * This is a permissive constraint but there needs + * to be _a_ constraint. + * This input is not required, but if present 'quotes' + * is required as it is the only field. + * Limited to 300 characters per quote (longer quotes + * will not be rejected, but will be truncated). + */ + quotes: Array; +}; + +export type ShareNotFound = { + __typename?: 'ShareNotFound'; + message?: Maybe; +}; + +export type ShareResult = PocketShare | ShareNotFound; + /** A user-created list of Pocket saves that can be shared publicly. */ export type ShareableList = { __typename?: 'ShareableList'; @@ -3211,6 +3450,7 @@ export type User = { premiumFeatures?: Maybe>>; /** Current premium status of the user */ premiumStatus?: Maybe; + recentSearches?: Maybe>; /** Preferences for recommendations that the user has explicitly set. */ recommendationPreferences?: Maybe; /** Get a PocketSave(s) by its id(s) */ @@ -3396,6 +3636,7 @@ export type NewTabRecommendationsQueryVariables = Exact<{ locale: Scalars['String']; region?: InputMaybe; count?: InputMaybe; + enableRankingByRegion?: InputMaybe; }>; @@ -3403,4 +3644,4 @@ export type NewTabRecommendationsQuery = { __typename?: 'Query', newTabSlate: { export const RecentSavesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"RecentSaves"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"savedItems"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"statuses"},"value":{"kind":"ListValue","values":[{"kind":"EnumValue","value":"UNREAD"}]}}]}},{"kind":"Argument","name":{"kind":"Name","value":"sort"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"sortBy"},"value":{"kind":"EnumValue","value":"CREATED_AT"}},{"kind":"ObjectField","name":{"kind":"Name","value":"sortOrder"},"value":{"kind":"EnumValue","value":"DESC"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"item"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Item"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"wordCount"}},{"kind":"Field","name":{"kind":"Name","value":"topImage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"resolvedUrl"}},{"kind":"Field","name":{"kind":"Name","value":"givenUrl"}},{"kind":"Field","name":{"kind":"Name","value":"excerpt"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}}]}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const NewTabRecommendationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"NewTabRecommendations"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"locale"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"region"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"count"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"newTabSlate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"locale"},"value":{"kind":"Variable","name":{"kind":"Name","value":"locale"}}},{"kind":"Argument","name":{"kind":"Name","value":"region"},"value":{"kind":"Variable","name":{"kind":"Name","value":"region"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"utmSource"}},{"kind":"Field","name":{"kind":"Name","value":"recommendations"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"count"},"value":{"kind":"Variable","name":{"kind":"Name","value":"count"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"tileId"}},{"kind":"Field","name":{"kind":"Name","value":"corpusItem"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"excerpt"}},{"kind":"Field","name":{"kind":"Name","value":"imageUrl"}},{"kind":"Field","name":{"kind":"Name","value":"publisher"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const NewTabRecommendationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"NewTabRecommendations"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"locale"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"region"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"count"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"enableRankingByRegion"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"newTabSlate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"locale"},"value":{"kind":"Variable","name":{"kind":"Name","value":"locale"}}},{"kind":"Argument","name":{"kind":"Name","value":"region"},"value":{"kind":"Variable","name":{"kind":"Name","value":"region"}}},{"kind":"Argument","name":{"kind":"Name","value":"enableRankingByRegion"},"value":{"kind":"Variable","name":{"kind":"Name","value":"enableRankingByRegion"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"utmSource"}},{"kind":"Field","name":{"kind":"Name","value":"recommendations"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"count"},"value":{"kind":"Variable","name":{"kind":"Name","value":"count"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"tileId"}},{"kind":"Field","name":{"kind":"Name","value":"corpusItem"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"excerpt"}},{"kind":"Field","name":{"kind":"Name","value":"imageUrl"}},{"kind":"Field","name":{"kind":"Name","value":"publisher"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/src/generated/openapi/types.ts b/src/generated/openapi/types.ts index 7595fdf94..6bb158bed 100644 --- a/src/generated/openapi/types.ts +++ b/src/generated/openapi/types.ts @@ -1,40 +1,36 @@ -// THIS FILE IS GENERATED, DO NOT EDIT! -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -/* eslint-disable prettier/prettier */ -/* tslint:disable */ -/* eslint:disable */ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ -/** Type helpers */ -type Without = { [P in Exclude]?: never }; -type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; -type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; export interface paths { "/desktop/v1/recommendations": { /** - * Gets a list of Recommendations for a Locale and Region. This operation is performed anonymously and requires no auth. + * Gets a list of Recommendations for a Locale and Region. This operation is performed anonymously and requires no auth. * @description Supports Fx desktop version 114 and up. */ get: operations["getRecommendations"]; }; "/v3/firefox/global-recs": { /** - * Used by older versions of Firefox to get a list of Recommendations for a Locale and Region. This operation is performed anonymously and requires no auth. - * @deprecated + * Used by older versions of Firefox to get a list of Recommendations for a Locale and Region. This operation is performed anonymously and requires no auth. + * @deprecated * @description Supports Fx desktop version 115 and below. */ get: operations["getGlobalRecs"]; }; "/desktop/v1/recent-saves": { /** - * Gets a list of the most recent saves for a specific user. + * Gets a list of the most recent saves for a specific user. * @description Supports Fx desktop version 113 and up. */ get: operations["getRecentSaves"]; }; } +export type webhooks = Record; + export interface components { schemas: { Error: { @@ -56,11 +52,11 @@ export interface components { }; ErrorResponse: { /** @description An array of error objects */ - errors: (components["schemas"]["Error"])[]; + errors: components["schemas"]["Error"][]; }; Save: { /** - * @description Constant identifier for Saves, allowing them to be differentiated when multiple types are returned together. + * @description Constant identifier for Saves, allowing them to be differentiated when multiple types are returned together. * @enum {string} */ __typename: "Save"; @@ -85,7 +81,7 @@ export interface components { }; PendingSave: { /** - * @description Constant identifier for PendingSave, allowing them to be differentiated when multiple types are returned together. + * @description Constant identifier for PendingSave, allowing them to be differentiated when multiple types are returned together. * @enum {string} */ __typename: "PendingSave"; @@ -97,14 +93,14 @@ export interface components { /** @description These items contain similar content to saves, but have been through a curation process and have more guaranteed data. */ Recommendation: { /** - * @description Constant identifier for Recommendation type objects. + * @description Constant identifier for Recommendation type objects. * @enum {string} */ __typename: "Recommendation"; /** @description String identifier for the Recommendation. This value is expected to be different on each request. */ recommendationId?: string; /** - * @deprecated + * @deprecated * @description Numerical identifier for the Recommendation. This is specifically a number for Fx client and Mozilla data pipeline compatibility. This property will continue to be present because Firefox clients depend on it, but downstream users should use the recommendation id instead when available. */ tileId: number; @@ -134,12 +130,12 @@ export interface components { LegacySettings: { spocsPerNewTabs?: number; domainAffinityParameterSets?: Record; - timeSegments?: ({ + timeSegments?: { id: string; startTime: number; endTime: number; weightPosition: number; - })[]; + }[]; recsExpireTime?: number; version?: string; }; @@ -151,23 +147,27 @@ export interface components { pathItems: never; } +export type $defs = Record; + export type external = Record; export interface operations { + /** + * Gets a list of Recommendations for a Locale and Region. This operation is performed anonymously and requires no auth. + * @description Supports Fx desktop version 114 and up. + */ getRecommendations: { - /** - * Gets a list of Recommendations for a Locale and Region. This operation is performed anonymously and requires no auth. - * @description Supports Fx desktop version 114 and up. - */ parameters: { - /** @description The number of items to return. */ - /** @description This locale string is Fx domain language, and built from Fx expectations. Parameter values are not case sensitive. */ - /** @description This region string is Fx domain language, and built from Fx expectations. Parameter values are not case sensitive. See [Firefox Home & New Tab Regional Differences](https://mozilla-hub.atlassian.net/wiki/spaces/FPS/pages/80448805/Regional+Differences). */ query: { + /** @description The number of items to return. */ count?: number; + /** @description This locale string is Fx domain language, and built from Fx expectations. Parameter values are not case sensitive. */ locale: "fr" | "fr-FR" | "es" | "es-ES" | "it" | "it-IT" | "en" | "en-CA" | "en-GB" | "en-US" | "de" | "de-DE" | "de-AT" | "de-CH"; + /** @description This region string is Fx domain language, and built from Fx expectations. Parameter values are not case sensitive. See [Firefox Home & New Tab Regional Differences](https://mozilla-hub.atlassian.net/wiki/spaces/FPS/pages/80448805/Regional+Differences). */ region?: string; + /** @description Returns recommendations specific to the region if set to 1. */ + enableRankingByRegion?: 0 | 1; }; }; responses: { @@ -175,7 +175,7 @@ export interface operations { 200: { content: { "application/json": { - data: (components["schemas"]["Recommendation"])[]; + data: components["schemas"]["Recommendation"][]; }; }; }; @@ -186,7 +186,9 @@ export interface operations { }; }; /** @description This proxy service encountered an unexpected error. */ - 500: never; + 500: { + content: never; + }; /** @description Services downstream from this proxy encountered an unexpected error. */ 502: { content: { @@ -201,21 +203,21 @@ export interface operations { }; }; }; + /** + * Used by older versions of Firefox to get a list of Recommendations for a Locale and Region. This operation is performed anonymously and requires no auth. + * @deprecated + * @description Supports Fx desktop version 115 and below. + */ getGlobalRecs: { - /** - * Used by older versions of Firefox to get a list of Recommendations for a Locale and Region. This operation is performed anonymously and requires no auth. - * @deprecated - * @description Supports Fx desktop version 115 and below. - */ parameters: { - /** @description API version */ - /** @description Firefox locale */ - /** @description Firefox region */ - /** @description Maximum number of items to return */ query: { + /** @description API version */ version: number; + /** @description Firefox locale */ locale_lang: string; + /** @description Firefox region */ region?: string; + /** @description Maximum number of items to return */ count?: number; }; }; @@ -226,9 +228,9 @@ export interface operations { "application/json": { /** @enum {integer} */ status: 1; - spocs: (unknown)[]; + spocs: unknown[]; settings: components["schemas"]["LegacySettings"]; - recommendations: (components["schemas"]["LegacyFeedItem"])[]; + recommendations: components["schemas"]["LegacyFeedItem"][]; }; }; }; @@ -239,7 +241,9 @@ export interface operations { }; }; /** @description This proxy service encountered an unexpected error. */ - 500: never; + 500: { + content: never; + }; /** @description Services downstream from this proxy encountered an unexpected error. */ 502: { content: { @@ -254,14 +258,14 @@ export interface operations { }; }; }; + /** + * Gets a list of the most recent saves for a specific user. + * @description Supports Fx desktop version 113 and up. + */ getRecentSaves: { - /** - * Gets a list of the most recent saves for a specific user. - * @description Supports Fx desktop version 113 and up. - */ - parameters?: { - /** @description The number of items to return. */ + parameters: { query?: { + /** @description The number of items to return. */ count?: number; }; }; @@ -287,7 +291,9 @@ export interface operations { }; }; /** @description This proxy service encountered an unexpected error. */ - 500: never; + 500: { + content: never; + }; /** @description Services downstream from this proxy encountered an unexpected error. */ 502: { content: { diff --git a/src/graphql-proxy/recommendations/Recommendations.graphql b/src/graphql-proxy/recommendations/Recommendations.graphql index fe51e30b0..b1db4714d 100644 --- a/src/graphql-proxy/recommendations/Recommendations.graphql +++ b/src/graphql-proxy/recommendations/Recommendations.graphql @@ -1,5 +1,5 @@ -query NewTabRecommendations($locale: String!, $region: String, $count: Int) { - newTabSlate(locale: $locale, region: $region) { +query NewTabRecommendations($locale: String!, $region: String, $count: Int, $enableRankingByRegion: Boolean) { + newTabSlate(locale: $locale, region: $region, enableRankingByRegion: $enableRankingByRegion) { utmSource recommendations(count: $count) { id