diff --git a/.vscode/settings.json b/.vscode/settings.json index d9f2d18f6b4..cc1f6c98b83 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,5 @@ { - "editor.rulers": [ - 80 - ], + "editor.rulers": [80], "editor.tabSize": 2, "javascript.validate.enable": true, "search.exclude": { @@ -16,7 +14,7 @@ "editor.formatOnSave": false }, "typescript.tsdk": "./node_modules/typescript/lib", - "debug.node.autoAttach": "on", + "debug.node.autoAttach": "off", "cSpell.ignoreWords": [], "cSpell.diagnosticLevel": "Hint", "cSpell.ignorePaths": [ @@ -34,4 +32,4 @@ "source.fixAll.eslint": true, "source.fixAll.stylelint": true } -} \ No newline at end of file +} diff --git a/docs/creating_review_app.md b/docs/creating_review_app.md index cdd8e9dd6ad..cd6470a1771 100644 --- a/docs/creating_review_app.md +++ b/docs/creating_review_app.md @@ -36,6 +36,19 @@ CircleCI will match the `review-app-` prefix and either: 1. Create a review app using `build_review_app.sh` if the review app doesn't exist yet (i.e. first successful push of the branch), or 2. Update an existing review app using `update_review_app.sh` +3. Once CI is complete follow [this step](https://github.com/artsy/force/blob/master/docs/creating_review_app.md#dns-setup) to setup DNS. + +> Once you're done working with the review app it (currently) has to be deleted manually + +#### Deleting a Review App + +Run: + +```sh +yarn delete-review-app +``` + +The `name` is the name of the branch, minus the `review-app-` prefix. #### Manual Steps diff --git a/package.json b/package.json index f34ff2e671d..67160a5d18f 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "compile": "babel src/v2 --out-dir dist/v2 -s --source-map --extensions '.js,.jsx,.ts,.tsx' --ignore src/v2/**/__tests__,src/v2/**/__stories__", "cypress:run": "./node_modules/.bin/cypress run", "cypress": "./node_modules/.bin/cypress open", + "delete-review-app": "kubectl --context staging delete namespace", "jest": "JEST_JUNIT_OUTPUT=reports/junit/js-test-results.xml node --expose-gc --max_old_space_size=4096 ./node_modules/.bin/jest --no-cache --runInBand --logHeapUsage", "lint": "eslint --cache --cache-location '.cache/eslint/' --ext ts,tsx --ignore-pattern 'src/v2/__generated__'", "mocha": "scripts/mocha.sh", @@ -62,9 +63,9 @@ "@artsy/cohesion": "1.28.0", "@artsy/express-reloadable": "1.4.8", "@artsy/gemup": "0.1.0", - "@artsy/palette": "12.0.2", + "@artsy/palette": "12.1.0", "@artsy/passport": "1.3.2", - "@artsy/reaction": "27.1.0", + "@artsy/reaction": "28.0.0", "@artsy/stitch": "6.1.6", "@artsy/xapp": "1.0.6", "@loadable/component": "5.12.0", @@ -104,14 +105,15 @@ "express": "4.16.4", "express-ipfilter": "0.2.1", "express-request-id": "1.4.0", + "farce": "0.4.5", "fastclick": "1.0.6", "flickity": "2.1.2", "flickity-imagesloaded": "2.0.0", "focus-visible": "5.1.0", "forever": "0.15.3", - "found": "0.4.9", - "found-relay": "0.5.0", - "found-scroll": "0.1.6", + "found": "0.5.5", + "found-relay": "0.8.2", + "found-scroll": "0.3.0", "geoformatter": "artsy/geoformatter", "geolib": "2.0.22", "glob": "7.1.3", @@ -167,9 +169,9 @@ "react-dom": "16.8.6", "react-linkify": "1.0.0-alpha", "react-redux": "5.1.1", - "react-relay": "7.1.0", - "react-relay-network-modern": "2.5.1", - "react-relay-network-modern-ssr": "1.2.2", + "react-relay": "9.1.0", + "react-relay-network-modern": "4.7.3", + "react-relay-network-modern-ssr": "1.4.0", "react-remove-scroll": "2.3.0", "react-router": "4.2.0", "react-router-redux": "4.0.8", @@ -183,7 +185,7 @@ "redux-thunk": "2.2.0", "referer-parser": "0.0.3", "relay-mock-network-layer": "2.0.0", - "relay-runtime": "7.1.0", + "relay-runtime": "9.1.0", "require-control": "2.1.1", "sailthru-client": "3.0.2", "scroll-frame": "1.0.0", @@ -256,7 +258,7 @@ "@types/react-tracking": "7.0.0", "@types/react-transition-group": "2.0.11", "@types/redis": "2.8.22", - "@types/relay-runtime": "8.0.10", + "@types/relay-runtime": "9.1.0", "@types/storybook__react": "5.2.1", "@types/styled-components": "4.0.3", "@types/styled-system": "5.1.9", @@ -271,7 +273,7 @@ "babel-plugin-inline-react-svg": "0.2.0", "babel-plugin-lodash": "3.3.4", "babel-plugin-module-resolver": "3.1.0", - "babel-plugin-relay": "7.1.0", + "babel-plugin-relay": "9.0.0", "babel-plugin-styled-components": "1.11.1", "benv": "3.3.0", "cache-loader": "1.2.2", @@ -321,9 +323,9 @@ "react-stripe-elements": "2.0.1", "react-test-renderer": "16.8.6", "react-use-dimensions": "1.2.1", - "relay-compiler": "7.1.0", - "relay-compiler-language-typescript": "10.1.0", - "relay-config": "7.1.0", + "relay-compiler": "9.1.0", + "relay-compiler-language-typescript": "12.0.3", + "relay-config": "9.1.0", "rewire": "2.2.0", "s3": "4.4.0", "should": "11.2.1", diff --git a/patches/found+0.4.9.patch b/patches/found+0.5.5.patch similarity index 70% rename from patches/found+0.4.9.patch rename to patches/found+0.5.5.patch index 36cb9491e71..bdf7f2a1c02 100644 --- a/patches/found+0.4.9.patch +++ b/patches/found+0.5.5.patch @@ -1,7 +1,7 @@ -diff --git a/node_modules/found/lib/index.d.ts b/node_modules/found/lib/index.d.ts -index 02cc90c..b3706dd 100644 ---- a/node_modules/found/lib/index.d.ts -+++ b/node_modules/found/lib/index.d.ts +diff --git a/node_modules/found/index.d.ts b/node_modules/found/index.d.ts +index 6868cf5..4bca495 100644 +--- a/node_modules/found/index.d.ts ++++ b/node_modules/found/index.d.ts @@ -111,6 +111,10 @@ declare module 'found' { * matchContext from the router */ @@ -13,7 +13,7 @@ index 02cc90c..b3706dd 100644 } interface FoundState { -@@ -267,6 +271,12 @@ declare module 'found' { +@@ -272,6 +276,12 @@ declare module 'found' { * loaded */ data?: any; @@ -26,10 +26,10 @@ index 02cc90c..b3706dd 100644 } /** -@@ -438,6 +448,8 @@ declare module 'found' { +@@ -444,6 +454,8 @@ declare module 'found' { function useRouter(): RouterState; - + + const RouterContext: React.Context; + function withRouter( diff --git a/patches/relay-compiler+7.1.0.patch b/patches/relay-compiler+9.1.0.patch similarity index 86% rename from patches/relay-compiler+7.1.0.patch rename to patches/relay-compiler+9.1.0.patch index 3337eeb8919..5fc6158daf9 100644 --- a/patches/relay-compiler+7.1.0.patch +++ b/patches/relay-compiler+9.1.0.patch @@ -1,17 +1,17 @@ diff --git a/node_modules/relay-compiler/bin/relay-compiler b/node_modules/relay-compiler/bin/relay-compiler -index 78b40a3..8d05a9a 100755 +index 02c8adc..e74249f 100755 --- a/node_modules/relay-compiler/bin/relay-compiler +++ b/node_modules/relay-compiler/bin/relay-compiler -@@ -308,7 +308,7 @@ function highlightSourceAtLocation(source, location) { +@@ -291,7 +291,7 @@ function highlightSourceAtLocation(source, location) { var columnOffset = location.line === 1 ? firstLineColumnOffset : 0; var columnNum = location.column + columnOffset; var lines = body.split(/\r\n|[\n\r]/g); - return "".concat(source.name, " (").concat(lineNum, ":").concat(columnNum, ")\n") + printPrefixedLines([// Lines specified like this: ["prefix", "string"], -+ return "".concat(source.name, ":").concat(lineNum, ":").concat(columnNum, "\n") + printPrefixedLines([// Lines specified like this: ["prefix", "string"], ++ return "".concat(source.name, ":").concat(lineNum, ":").concat(columnNum, ")\n") + printPrefixedLines([// Lines specified like this: ["prefix", "string"], ["".concat(lineNum - 1, ": "), lines[lineIndex - 1]], ["".concat(lineNum, ": "), lines[lineIndex]], ['', whitespace(columnNum - 1) + '^'], ["".concat(lineNum + 1, ": "), lines[lineIndex + 1]]]); } -@@ -17391,10 +17391,10 @@ module.exports = function (tagFinder) { +@@ -17783,10 +17783,10 @@ module.exports = function (tagFinder, getFileFilter) { var astDefinitions = []; var sources = []; @@ -25,7 +25,7 @@ index 78b40a3..8d05a9a 100755 sources.push(source.body); astDefinitions.push.apply(astDefinitions, (0, _toConsumableArray2["default"])(ast.definitions)); }); -@@ -17478,9 +17478,7 @@ function find(tagFinder, text, absPath) { +@@ -17871,9 +17871,7 @@ function find(tagFinder, text, absPath) { tags.forEach(function (tag) { return validateTemplate(tag, moduleName, absPath); }); diff --git a/patches/relay-runtime+7.1.0.patch b/patches/relay-runtime+9.1.0.patch similarity index 77% rename from patches/relay-runtime+7.1.0.patch rename to patches/relay-runtime+9.1.0.patch index 3860ce90c81..2052bb7d439 100644 --- a/patches/relay-runtime+7.1.0.patch +++ b/patches/relay-runtime+9.1.0.patch @@ -1,7 +1,8 @@ -patch-package +diff --git a/node_modules/relay-runtime/lib/store/RelayResponseNormalizer.js b/node_modules/relay-runtime/lib/store/RelayResponseNormalizer.js +index 13ac420..8502116 100644 --- a/node_modules/relay-runtime/lib/store/RelayResponseNormalizer.js +++ b/node_modules/relay-runtime/lib/store/RelayResponseNormalizer.js -@@ -407,7 +407,7 @@ function () { +@@ -278,7 +278,7 @@ var RelayResponseNormalizer = /*#__PURE__*/function () { // // Otherwise, missing fields usually indicate a server or user error ( // the latter for manually constructed payloads). diff --git a/src/v2/Apps/Artist/ArtistApp.tsx b/src/v2/Apps/Artist/ArtistApp.tsx index 9a84421717a..dee0a44f907 100644 --- a/src/v2/Apps/Artist/ArtistApp.tsx +++ b/src/v2/Apps/Artist/ArtistApp.tsx @@ -38,7 +38,9 @@ export const ArtistApp: React.FC = props => { const { artist, children } = props const { trackEvent } = useTracking() const route = findCurrentRoute(props.match) - let HorizontalPaddingArea = HorizontalPadding + let HorizontalPaddingArea: + | typeof HorizontalPadding + | typeof Box = HorizontalPadding let maxWidth if (route.displayFullPage) { diff --git a/src/v2/Apps/Artist/__tests__/routes.jest.tsx b/src/v2/Apps/Artist/__tests__/routes.jest.tsx index 31526162439..75229a1f30d 100644 --- a/src/v2/Apps/Artist/__tests__/routes.jest.tsx +++ b/src/v2/Apps/Artist/__tests__/routes.jest.tsx @@ -2,8 +2,7 @@ import { routes_ArtistTopLevelQueryRawResponse } from "v2/__generated__/routes_A import { routes } from "v2/Apps/Artist/routes" import { createMockNetworkLayer2 } from "v2/DevTools/createMockNetworkLayer" import { Resolver } from "found-relay" -import { FarceRedirectResult } from "found/lib/server" -import getFarceResult from "found/lib/server/getFarceResult" +import { FarceRedirectResult, getFarceResult } from "found/server" import React from "react" import { Environment, RecordSource, Store } from "relay-runtime" import { PermanentRedirectException } from "v2/Artsy/Router/PermanentRedirectException" diff --git a/src/v2/Apps/ArtistSeries/ArtistSeriesApp.tsx b/src/v2/Apps/ArtistSeries/ArtistSeriesApp.tsx index a415832808c..3a410d38c50 100644 --- a/src/v2/Apps/ArtistSeries/ArtistSeriesApp.tsx +++ b/src/v2/Apps/ArtistSeries/ArtistSeriesApp.tsx @@ -11,6 +11,7 @@ import { ArtistSeriesArtworksFilterRefetchContainer as ArtistSeriesArtworksFilte import { userHasLabFeature } from "v2/Utils/user" import { ErrorPage } from "v2/Components/ErrorPage" import { ArtistSeriesRailFragmentContainer as OtherArtistSeriesRail } from "v2/Components/ArtistSeriesRail/ArtistSeriesRail" +import { ArtistSeriesMetaFragmentContainer as ArtistSeriesMeta } from "./Components/ArtistSeriesMeta" interface ArtistSeriesAppProps { artistSeries: ArtistSeriesApp_artistSeries @@ -24,6 +25,8 @@ const ArtistSeriesApp: React.FC = ({ artistSeries }) => { const { railArtist } = artistSeries return ( + {/* NOTE: react-head automatically moves these tags to the element */} + @@ -67,6 +70,7 @@ export default createFragmentContainer(ArtistSeriesApp, { sort: { type: "String", defaultValue: "-partner_updated_at" } width: { type: "String" } ) { + ...ArtistSeriesMeta_artistSeries ...ArtistSeriesHeader_artistSeries railArtist: artists(size: 1) { ...ArtistSeriesRail_artist diff --git a/src/v2/Apps/ArtistSeries/Components/ArtistSeriesMeta.tsx b/src/v2/Apps/ArtistSeries/Components/ArtistSeriesMeta.tsx new file mode 100644 index 00000000000..5008bdea691 --- /dev/null +++ b/src/v2/Apps/ArtistSeries/Components/ArtistSeriesMeta.tsx @@ -0,0 +1,47 @@ +import React from "react" +import { createFragmentContainer, graphql } from "react-relay" +import { Meta, Title } from "react-head" +import { ArtistSeriesMeta_artistSeries } from "v2/__generated__/ArtistSeriesMeta_artistSeries.graphql" +import { truncate } from "lodash" + +interface ArtistSeriesMetaProps { + artistSeries: ArtistSeriesMeta_artistSeries +} + +export const ArtistSeriesMeta: React.FC = props => { + const { artistSeries } = props + const artist = artistSeries.artists[0] + const artistName = artist?.name ? `${artist.name}’s ` : "" + const title = `${artistName}${artistSeries.title} - For Sale on Artsy` + const descriptionFirstSentence = `Discover and collect art from ${artistName}iconic ${artistSeries.title} series and more. ` + const description = truncate( + `${descriptionFirstSentence}${artistSeries.description}`, + { length: 160, separator: " " } + ) + + return ( + <> + {title} + + + + + + + ) +} + +export const ArtistSeriesMetaFragmentContainer = createFragmentContainer( + ArtistSeriesMeta, + { + artistSeries: graphql` + fragment ArtistSeriesMeta_artistSeries on ArtistSeries { + title + description + artists(size: 1) { + name + } + } + `, + } +) diff --git a/src/v2/Apps/ArtistSeries/__tests__/ArtistSeriesApp.jest.tsx b/src/v2/Apps/ArtistSeries/__tests__/ArtistSeriesApp.jest.tsx index a8d43cbe516..5cec3da11f1 100644 --- a/src/v2/Apps/ArtistSeries/__tests__/ArtistSeriesApp.jest.tsx +++ b/src/v2/Apps/ArtistSeries/__tests__/ArtistSeriesApp.jest.tsx @@ -55,6 +55,7 @@ describe("ArtistSeriesApp", () => { it("renders the correct components", async () => { const wrapper = await getWrapper() expect(wrapper.find("AppContainer").length).toBe(1) + expect(wrapper.find("ArtistSeriesMeta").length).toBe(1) expect(wrapper.find("ArtistSeriesHeader").length).toBe(1) expect(wrapper.find("ArtistSeriesArtworksFilter").length).toBe(1) expect(wrapper.find("ArtistSeriesRail").length).toBe(1) @@ -219,6 +220,7 @@ const ArtistSeriesAppFixture: ArtistSeriesApp_QueryRawResponse = { ], title: "Pumpkins", artworksCountMessage: "20 available", + description: "All of the pumpkins", descriptionFormatted: "All of the pumpkins", image: { url: "https://test.artsy.net/pumpkins-header-image.jpg", diff --git a/src/v2/Apps/ArtistSeries/__tests__/ArtistSeriesMeta.jest.tsx b/src/v2/Apps/ArtistSeries/__tests__/ArtistSeriesMeta.jest.tsx new file mode 100644 index 00000000000..83dc55eba74 --- /dev/null +++ b/src/v2/Apps/ArtistSeries/__tests__/ArtistSeriesMeta.jest.tsx @@ -0,0 +1,94 @@ +import React from "react" +import { MockBoot, renderRelayTree } from "v2/DevTools" +import { ArtistSeriesMetaFragmentContainer } from "../Components/ArtistSeriesMeta" +import { graphql } from "react-relay" +import { ArtistSeriesMeta_TestQueryRawResponse } from "v2/__generated__/ArtistSeriesMeta_TestQuery.graphql" + +jest.unmock("react-relay") + +describe("ArtistSeriesMeta", () => { + const getWrapper = async ( + response: ArtistSeriesMeta_TestQueryRawResponse = ArtistSeriesMetaFixture + ) => { + return renderRelayTree({ + Component: ({ artistSeries }) => { + return ( + + + + ) + }, + query: graphql` + query ArtistSeriesMeta_TestQuery($slug: ID!) @raw_response_type { + artistSeries(id: $slug) { + ...ArtistSeriesMeta_artistSeries + } + } + `, + variables: { + slug: "pumpkins", + }, + mockData: response, + }) + } + it("creates search and social meta tags for title", async () => { + const wrapper = await getWrapper() + const expectedTitle = "Yayoi Kusama’s Pumpkins - For Sale on Artsy" + expect(wrapper.find("Title").text()).toBe(expectedTitle) + expect(wrapper.find('Meta[property="og:title"]').prop("content")).toBe( + expectedTitle + ) + expect(wrapper.find('Meta[property="twitter:title"]').prop("content")).toBe( + expectedTitle + ) + }) + + it("creates search and social meta tags for description", async () => { + const wrapper = await getWrapper() + const expectedDescription = + "Discover and collect art from Yayoi Kusama’s iconic Pumpkins series and more. Yayoi Kusama's instantly recognizable pumpkins have become fixtures of..." + expect(wrapper.find('Meta[name="description"]').prop("content")).toBe( + expectedDescription + ) + expect( + wrapper.find('Meta[property="og:description"]').prop("content") + ).toBe(expectedDescription) + expect( + wrapper.find('Meta[property="twitter:description"]').prop("content") + ).toBe(expectedDescription) + }) + + it("omits the artist name in the title an description when there is no artist", async () => { + const wrapper = await getWrapper(ArtistSeriesMetaFixtureNoArtist) + const expectedTitle = "Squashes - For Sale on Artsy" + const expectedDescription = + "Discover and collect art from iconic Squashes series and more. Squashes are a lot like pumpkins except that they don't belong to any particular holiday. The..." + expect(wrapper.find("Title").text()).toBe(expectedTitle) + expect(wrapper.find('Meta[name="description"]').prop("content")).toBe( + expectedDescription + ) + }) +}) + +const ArtistSeriesMetaFixture: ArtistSeriesMeta_TestQueryRawResponse = { + artistSeries: { + title: "Pumpkins", + description: + "Yayoi Kusama's instantly recognizable pumpkins have become fixtures of contemporary art and popular culture—and have helped make her one of the world’s most expensive living female artists. Kusama began drawing pumpkins as a child in pre-war Japan, where her family owned a plant nursery that farmed kabocha squash. Her obsession only grew with time. Kusama’s signature dotted pumpkins have since appeared as works on paper, handheld figurines, towering sculptures, and in the artist’s immersive infinity room installations. To Kusama, the pumpkins are warm and humorous motifs with figures that, at times, feel human. “I was enchanted by their charming and winsome form,” Kusama once said. “What appealed to me most was the pumpkin's generous unpretentiousness.”", + artists: [ + { + name: "Yayoi Kusama", + id: "abc123", + }, + ], + }, +} + +const ArtistSeriesMetaFixtureNoArtist: ArtistSeriesMeta_TestQueryRawResponse = { + artistSeries: { + title: "Squashes", + description: + "Squashes are a lot like pumpkins except that they don't belong to any particular holiday. The most independent gourd, they always remain in style throughout the year.", + artists: [], + }, +} diff --git a/src/v2/Apps/Auction/__tests__/routes.jest.ts b/src/v2/Apps/Auction/__tests__/routes.jest.ts index fad28463f42..dfb438ef878 100644 --- a/src/v2/Apps/Auction/__tests__/routes.jest.ts +++ b/src/v2/Apps/Auction/__tests__/routes.jest.ts @@ -7,8 +7,8 @@ import deepMerge from "deepmerge" import { createMockNetworkLayer2 } from "v2/DevTools/createMockNetworkLayer" import { createRender } from "found" import { Resolver } from "found-relay" -import { FarceElementResult, FarceRedirectResult } from "found/lib/server" -import getFarceResult from "found/lib/server/getFarceResult" +import { FarceElementResult, FarceRedirectResult } from "found/server" +import { getFarceResult } from "found/server" import { Environment, RecordSource, Store } from "relay-runtime" import { DeepPartial } from "v2/Utils/typeSupport" diff --git a/src/v2/Apps/Conversation/Components/TimeSince.tsx b/src/v2/Apps/Conversation/Components/TimeSince.tsx index 13cdafa015f..61a10d1f6ae 100644 --- a/src/v2/Apps/Conversation/Components/TimeSince.tsx +++ b/src/v2/Apps/Conversation/Components/TimeSince.tsx @@ -73,7 +73,7 @@ interface TimeSinceProps extends Omit { size?: SansSize time: string exact?: boolean - style?: React.CSSProperties + style?: any // FIXME: React.CSSProperties } export const TimeSince: React.FC = ({ size = "2", diff --git a/src/v2/Apps/Fair/Components/FairHeader.tsx b/src/v2/Apps/Fair/Components/FairHeader.tsx new file mode 100644 index 00000000000..db4354eaa6a --- /dev/null +++ b/src/v2/Apps/Fair/Components/FairHeader.tsx @@ -0,0 +1,44 @@ +import React from "react" +import { Placeholder } from "v2/Utils" +import { Col, Flex, Row, Spacer, Text } from "@artsy/palette" +import { createFragmentContainer, graphql } from "react-relay" +import { FairHeader_fair } from "v2/__generated__/FairHeader_fair.graphql" + +interface FairHeaderProps { + fair: FairHeader_fair +} + +const FairHeader: React.FC = ({ fair }) => { + return ( + <> + + + + + + + + {fair.name} + {fair.formattedOpeningHours} + + + {fair.about} + + + + ) +} + +export const FairHeaderFragmentContainer = createFragmentContainer(FairHeader, { + fair: graphql` + fragment FairHeader_fair on Fair { + about + formattedOpeningHours + name + } + `, +}) diff --git a/src/v2/Apps/Fair/FairApp.tsx b/src/v2/Apps/Fair/FairApp.tsx new file mode 100644 index 00000000000..813cf55c0d3 --- /dev/null +++ b/src/v2/Apps/Fair/FairApp.tsx @@ -0,0 +1,40 @@ +import React from "react" +import { AppContainer } from "v2/Apps/Components/AppContainer" +import { createFragmentContainer, graphql } from "react-relay" +import { FairApp_fair } from "v2/__generated__/FairApp_fair.graphql" +import { Separator } from "@artsy/palette" +import { HorizontalPadding } from "v2/Apps/Components/HorizontalPadding" +import { Footer } from "v2/Components/Footer" +import { FairHeaderFragmentContainer } from "./Components/FairHeader" +import { ErrorPage } from "v2/Components/ErrorPage" + +interface FairAppProps { + fair: FairApp_fair +} + +const FairApp: React.FC = ({ fair }) => { + if (!fair) return + + return ( + <> + + + + + + +