feat: graphql introspection, timestamps, custom scalar skaffolding

This commit is contained in:
Vyacheslav Matyukhin 2022-04-20 02:50:19 +04:00
parent 4397a310fe
commit 297eadc59b
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
11 changed files with 433 additions and 96 deletions

View File

@ -14,11 +14,19 @@ generates:
plugins:
- typescript
src/graphql/introspection.json:
plugins:
- introspection:
minify: true
src/:
preset: near-operation-file
presetConfig:
extension: .generated.tsx
baseTypesPath: graphql/types.generated.ts
plugins:
- typescript-operations
- typescript-operations:
strictScalars: true
scalars:
Date: number
- typed-document-node

415
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -88,10 +88,12 @@
"textversionjs": "^1.1.3",
"ts-node": "^10.7.0",
"tunnel": "^0.0.6",
"urql": "^2.2.0"
"urql": "^2.2.0",
"urql-custom-scalars-exchange": "^0.1.5"
},
"devDependencies": {
"@graphql-codegen/cli": "^2.6.2",
"@graphql-codegen/introspection": "^2.1.1",
"@graphql-codegen/near-operation-file-preset": "^2.2.9",
"@graphql-codegen/schema-ast": "^2.4.1",
"@graphql-codegen/typed-document-node": "^2.2.8",

View File

@ -1,9 +0,0 @@
export interface DashboardItem {
id: string;
title: string;
description: string;
contents: any;
timestamp: string;
creator: string;
extra: any;
}

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@ import { AppProps } from "next/app";
import Router from "next/router";
import NProgress from "nprogress";
import { graphqlEndpoint } from "../web/urql";
import { getUrqlClientOptions } from "../web/urql";
Router.events.on("routeChangeStart", (as, { shallow }) => {
if (!shallow) {
@ -25,9 +25,6 @@ function MyApp({ Component, pageProps }: AppProps) {
);
}
export default withUrqlClient(
() => ({
url: graphqlEndpoint,
}),
{ ssr: false }
)(MyApp);
export default withUrqlClient((ssr) => getUrqlClientOptions(ssr), {
ssr: false,
})(MyApp);

View File

@ -2,21 +2,21 @@ import * as Types from '../../graphql/types.generated';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
import { QuestionFragmentDoc } from '../search/queries.generated';
export type DashboardFragment = { __typename?: 'Dashboard', id: string, title: string, description: string, creator: string, questions: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: any, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } }> };
export type DashboardFragment = { __typename?: 'Dashboard', id: string, title: string, description: string, creator: string, questions: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } }> };
export type DashboardByIdQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type DashboardByIdQuery = { __typename?: 'Query', result: { __typename?: 'Dashboard', id: string, title: string, description: string, creator: string, questions: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: any, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } }> } };
export type DashboardByIdQuery = { __typename?: 'Query', result: { __typename?: 'Dashboard', id: string, title: string, description: string, creator: string, questions: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } }> } };
export type CreateDashboardMutationVariables = Types.Exact<{
input: Types.CreateDashboardInput;
}>;
export type CreateDashboardMutation = { __typename?: 'Mutation', result: { __typename?: 'CreateDashboardResult', dashboard: { __typename?: 'Dashboard', id: string, title: string, description: string, creator: string, questions: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: any, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } }> } } };
export type CreateDashboardMutation = { __typename?: 'Mutation', result: { __typename?: 'CreateDashboardResult', dashboard: { __typename?: 'Dashboard', id: string, title: string, description: string, creator: string, questions: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } }> } } };
export const DashboardFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Dashboard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Dashboard"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"creator"}},{"kind":"Field","name":{"kind":"Name","value":"questions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Question"}}]}}]}},...QuestionFragmentDoc.definitions]} as unknown as DocumentNode<DashboardFragment, unknown>;
export const DashboardByIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DashboardById"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"result"},"name":{"kind":"Name","value":"dashboard"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Dashboard"}}]}}]}},...DashboardFragmentDoc.definitions]} as unknown as DocumentNode<DashboardByIdQuery, DashboardByIdQueryVariables>;

View File

@ -105,7 +105,7 @@ const getCurrencySymbolIfNeeded = ({
const showFirstQualityIndicator = ({
numforecasts,
timestamp,
lastUpdated,
showTimeStamp,
qualityindicators,
}) => {
@ -124,7 +124,7 @@ const showFirstQualityIndicator = ({
<circle cx="4" cy="4" r="4" fill="rgb(29, 78, 216)" />
</svg>
{`Last updated: ${
timestamp && !!timestamp.slice ? timestamp.slice(0, 10) : "unknown"
lastUpdated ? lastUpdated.toISOString().slice(0, 10) : "unknown"
}`}
</span>
);
@ -135,13 +135,13 @@ const showFirstQualityIndicator = ({
const displayQualityIndicators: React.FC<{
numforecasts: number;
timestamp: number;
lastUpdated: Date;
showTimeStamp: boolean;
qualityindicators: any;
platform: string; // id string - e.g. "goodjudgment", not "Good Judgment"
}> = ({
numforecasts,
timestamp,
lastUpdated,
showTimeStamp,
qualityindicators,
platform,
@ -151,7 +151,7 @@ const displayQualityIndicators: React.FC<{
<div className="text-sm">
{showFirstQualityIndicator({
numforecasts,
timestamp,
lastUpdated,
showTimeStamp,
qualityindicators,
})}
@ -238,7 +238,7 @@ interface Props {
platformLabel: string;
numforecasts: any;
qualityindicators: any;
timestamp: any;
lastUpdated: Date;
showTimeStamp: boolean;
expandFooterToFullWidth: boolean;
}
@ -249,7 +249,7 @@ export const QuestionFooter: React.FC<Props> = ({
platformLabel,
numforecasts,
qualityindicators,
timestamp,
lastUpdated,
showTimeStamp,
expandFooterToFullWidth,
}) => {
@ -288,7 +288,7 @@ export const QuestionFooter: React.FC<Props> = ({
>
{displayQualityIndicators({
numforecasts,
timestamp,
lastUpdated,
showTimeStamp,
qualityindicators,
platform,

View File

@ -254,13 +254,14 @@ const CopyText: React.FC<{ text: string; displayText: string }> = ({
</div>
);
const LastUpdated: React.FC<{ timestamp: string }> = ({ timestamp }) => (
const LastUpdated: React.FC<{ timestamp: Date }> = ({ timestamp }) => (
<div className="flex items-center">
<svg className="mt-1" height="10" width="16">
<circle cx="4" cy="4" r="4" fill="rgb(29, 78, 216)" />
</svg>
<span className="text-gray-600">
Last updated: {timestamp ? timestamp.slice(0, 10) : "unknown"}
Last updated:{" "}
{timestamp ? timestamp.toISOString().slice(0, 10) : "unknown"}
</span>
</div>
);
@ -290,6 +291,7 @@ export const DisplayQuestion: React.FC<Props> = ({
expandFooterToFullWidth,
showIdToggle,
}) => {
const lastUpdated = new Date(timestamp * 1000);
const displayTimestampAtBottom =
checkIfDisplayTimeStampAtBottom(qualityIndicators);
@ -331,7 +333,7 @@ export const DisplayQuestion: React.FC<Props> = ({
showTimeStamp && !displayTimestampAtBottom ? "sm:block" : ""
}`}
>
<LastUpdated timestamp={timestamp} />
<LastUpdated timestamp={lastUpdated} />
</div>
</div>
)}
@ -343,7 +345,7 @@ export const DisplayQuestion: React.FC<Props> = ({
showTimeStamp && !displayTimestampAtBottom ? "sm:block" : ""
} ml-6`}
>
<LastUpdated timestamp={timestamp} />
<LastUpdated timestamp={lastUpdated} />
</div>
</div>
)}
@ -368,7 +370,7 @@ export const DisplayQuestion: React.FC<Props> = ({
} self-center`}
>
{/* This one is exclusively for mobile*/}
<LastUpdated timestamp={timestamp} />
<LastUpdated timestamp={lastUpdated} />
</div>
<div className="w-full">
<QuestionFooter
@ -377,7 +379,7 @@ export const DisplayQuestion: React.FC<Props> = ({
platformLabel={platform.label}
numforecasts={qualityIndicators.numForecasts}
qualityindicators={qualityIndicators}
timestamp={timestamp}
lastUpdated={lastUpdated}
showTimeStamp={showTimeStamp && displayTimestampAtBottom}
expandFooterToFullWidth={expandFooterToFullWidth}
/>

View File

@ -1,19 +1,19 @@
import * as Types from '../../graphql/types.generated';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type QuestionFragment = { __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: any, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } };
export type QuestionFragment = { __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } };
export type FrontpageQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type FrontpageQuery = { __typename?: 'Query', result: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: any, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } }> };
export type FrontpageQuery = { __typename?: 'Query', result: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } }> };
export type SearchQueryVariables = Types.Exact<{
input: Types.SearchInput;
}>;
export type SearchQuery = { __typename?: 'Query', result: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: any, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } }> };
export type SearchQuery = { __typename?: 'Query', result: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | null }>, platform: { __typename?: 'Platform', id: string, label: string }, qualityIndicators: { __typename?: 'QualityIndicators', stars: number, numForecasts?: number | null } }> };
export const QuestionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Question"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Question"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"options"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"probability"}}]}},{"kind":"Field","name":{"kind":"Name","value":"platform"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}}]}},{"kind":"Field","name":{"kind":"Name","value":"qualityIndicators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stars"}},{"kind":"Field","name":{"kind":"Name","value":"numForecasts"}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualization"}}]}}]} as unknown as DocumentNode<QuestionFragment, unknown>;
export const FrontpageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Frontpage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"result"},"name":{"kind":"Name","value":"frontpage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Question"}}]}}]}},...QuestionFragmentDoc.definitions]} as unknown as DocumentNode<FrontpageQuery, FrontpageQueryVariables>;

View File

@ -1,19 +1,40 @@
import { initUrqlClient } from "next-urql";
import { initUrqlClient, SSRExchange } from "next-urql";
import { cacheExchange, dedupExchange, fetchExchange, ssrExchange } from "urql";
import customScalarsExchange from "urql-custom-scalars-exchange";
import schema from "../graphql/introspection.json";
import { getBasePath } from "./utils";
export const graphqlEndpoint = `${getBasePath()}/api/graphql`;
const scalarsExchange = customScalarsExchange({
// Types don't match for some reason.
// Related:
// - https://github.com/apollographql/apollo-tooling/issues/1491
// - https://spectrum.chat/urql/help/schema-property-kind-is-missing-in-type~29c8f416-068c-485a-adf1-935686b99d05
schema: schema as any,
scalars: {
/* not compatible with next.js serialization limitations, unfortunately */
// Date(value: number) {
// return new Date(value * 1000);
// },
},
});
export const getUrqlClientOptions = (ssr: SSRExchange) => ({
url: graphqlEndpoint,
exchanges: [
dedupExchange,
scalarsExchange,
cacheExchange,
ssr,
fetchExchange,
],
});
// for getServerSideProps/getStaticProps only
export const ssrUrql = () => {
const ssrCache = ssrExchange({ isClient: false });
const client = initUrqlClient(
{
url: graphqlEndpoint,
exchanges: [dedupExchange, cacheExchange, ssrCache, fetchExchange],
},
false
);
const client = initUrqlClient(getUrqlClientOptions(ssrCache), false);
return [ssrCache, client] as const;
};