refactor: options code; also gql-gen & new timestamps

This commit is contained in:
Vyacheslav Matyukhin 2022-05-26 16:27:50 +04:00
parent 05c87e0e17
commit a6e6053c63
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
16 changed files with 186 additions and 178 deletions

View File

@ -41,6 +41,14 @@ scalar Date
type History implements QuestionShape {
description: String!
"""Last timestamp at which metaforecast fetched the question"""
fetched: Date!
"""
Last timestamp at which metaforecast fetched the question, in ISO 8601 format
"""
fetchedStr: String!
"""History items are identified by their integer ids"""
id: ID!
options: [ProbabilityOption!]!
@ -50,8 +58,8 @@ type History implements QuestionShape {
"""Unique string which identifies the question"""
questionId: ID!
"""Timestamp at which metaforecast fetched the question"""
timestamp: Date!
"""Last timestamp at which metaforecast fetched the question"""
timestamp: Date! @deprecated(reason: "Renamed to `fetched`")
title: String!
"""
@ -112,15 +120,15 @@ type QualityIndicators {
type Query {
"""Look up a single dashboard by its id"""
dashboard(id: ID!): Dashboard!
dashboard(id: ID!): Dashboard
"""Get a list of questions that are currently on the frontpage"""
frontpage: [Question!]!
platforms: [Platform!]!
"""Look up a single question by its id"""
question(id: ID!): Question!
questions(after: String, before: String, first: Int, last: Int): QueryQuestionsConnection!
question(id: ID!): Question
questions(after: String, before: String, first: Int, last: Int, orderBy: QuestionsOrderBy): QueryQuestionsConnection!
"""
Search for questions; uses Algolia instead of the primary metaforecast database
@ -140,6 +148,22 @@ type QueryQuestionsConnectionEdge {
type Question implements QuestionShape {
description: String!
"""Last timestamp at which metaforecast fetched the question"""
fetched: Date!
"""
Last timestamp at which metaforecast fetched the question, in ISO 8601 format
"""
fetchedStr: String!
"""First timestamp at which metaforecast fetched the question"""
firstSeen: Date!
"""
First timestamp at which metaforecast fetched the question, in ISO 8601 format
"""
firstSeenStr: String!
history: [History!]!
"""Unique string which identifies the question"""
@ -148,8 +172,8 @@ type Question implements QuestionShape {
platform: Platform!
qualityIndicators: QualityIndicators!
"""Timestamp at which metaforecast fetched the question"""
timestamp: Date!
"""Last timestamp at which metaforecast fetched the question"""
timestamp: Date! @deprecated(reason: "Renamed to `fetched`")
title: String!
"""
@ -161,12 +185,20 @@ type Question implements QuestionShape {
interface QuestionShape {
description: String!
"""Last timestamp at which metaforecast fetched the question"""
fetched: Date!
"""
Last timestamp at which metaforecast fetched the question, in ISO 8601 format
"""
fetchedStr: String!
options: [ProbabilityOption!]!
platform: Platform!
qualityIndicators: QualityIndicators!
"""Timestamp at which metaforecast fetched the question"""
timestamp: Date!
"""Last timestamp at which metaforecast fetched the question"""
timestamp: Date! @deprecated(reason: "Renamed to `fetched`")
title: String!
"""
@ -175,6 +207,10 @@ interface QuestionShape {
url: String!
}
enum QuestionsOrderBy {
FIRST_SEEN_DESC
}
input SearchInput {
"""List of platform ids to filter by"""
forecastingPlatforms: [String!]

File diff suppressed because one or more lines are too long

View File

@ -46,6 +46,10 @@ export type Dashboard = {
export type History = QuestionShape & {
__typename?: 'History';
description: Scalars['String'];
/** Last timestamp at which metaforecast fetched the question */
fetched: Scalars['Date'];
/** Last timestamp at which metaforecast fetched the question, in ISO 8601 format */
fetchedStr: Scalars['String'];
/** History items are identified by their integer ids */
id: Scalars['ID'];
options: Array<ProbabilityOption>;
@ -53,7 +57,10 @@ export type History = QuestionShape & {
qualityIndicators: QualityIndicators;
/** Unique string which identifies the question */
questionId: Scalars['ID'];
/** Timestamp at which metaforecast fetched the question */
/**
* Last timestamp at which metaforecast fetched the question
* @deprecated Renamed to `fetched`
*/
timestamp: Scalars['Date'];
title: Scalars['String'];
/** Non-unique, a very small number of platforms have a page for more than one prediction */
@ -114,12 +121,12 @@ export type QualityIndicators = {
export type Query = {
__typename?: 'Query';
/** Look up a single dashboard by its id */
dashboard: Dashboard;
dashboard?: Maybe<Dashboard>;
/** Get a list of questions that are currently on the frontpage */
frontpage: Array<Question>;
platforms: Array<Platform>;
/** Look up a single question by its id */
question: Question;
question?: Maybe<Question>;
questions: QueryQuestionsConnection;
/** Search for questions; uses Algolia instead of the primary metaforecast database */
searchQuestions: Array<Question>;
@ -141,6 +148,7 @@ export type QueryQuestionsArgs = {
before?: InputMaybe<Scalars['String']>;
first?: InputMaybe<Scalars['Int']>;
last?: InputMaybe<Scalars['Int']>;
orderBy?: InputMaybe<QuestionsOrderBy>;
};
@ -163,13 +171,24 @@ export type QueryQuestionsConnectionEdge = {
export type Question = QuestionShape & {
__typename?: 'Question';
description: Scalars['String'];
/** Last timestamp at which metaforecast fetched the question */
fetched: Scalars['Date'];
/** Last timestamp at which metaforecast fetched the question, in ISO 8601 format */
fetchedStr: Scalars['String'];
/** First timestamp at which metaforecast fetched the question */
firstSeen: Scalars['Date'];
/** First timestamp at which metaforecast fetched the question, in ISO 8601 format */
firstSeenStr: Scalars['String'];
history: Array<History>;
/** Unique string which identifies the question */
id: Scalars['ID'];
options: Array<ProbabilityOption>;
platform: Platform;
qualityIndicators: QualityIndicators;
/** Timestamp at which metaforecast fetched the question */
/**
* Last timestamp at which metaforecast fetched the question
* @deprecated Renamed to `fetched`
*/
timestamp: Scalars['Date'];
title: Scalars['String'];
/** Non-unique, a very small number of platforms have a page for more than one prediction */
@ -179,16 +198,27 @@ export type Question = QuestionShape & {
export type QuestionShape = {
description: Scalars['String'];
/** Last timestamp at which metaforecast fetched the question */
fetched: Scalars['Date'];
/** Last timestamp at which metaforecast fetched the question, in ISO 8601 format */
fetchedStr: Scalars['String'];
options: Array<ProbabilityOption>;
platform: Platform;
qualityIndicators: QualityIndicators;
/** Timestamp at which metaforecast fetched the question */
/**
* Last timestamp at which metaforecast fetched the question
* @deprecated Renamed to `fetched`
*/
timestamp: Scalars['Date'];
title: Scalars['String'];
/** Non-unique, a very small number of platforms have a page for more than one prediction */
url: Scalars['String'];
};
export enum QuestionsOrderBy {
FirstSeenDesc = 'FIRST_SEEN_DESC'
}
export type SearchInput = {
/** List of platform ids to filter by */
forecastingPlatforms?: InputMaybe<Array<Scalars['String']>>;

View File

@ -35,7 +35,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
props: {
// reduntant: page component doesn't do graphql requests, but it's still nice/more consistent to have data in cache
urqlState: ssrCache.extractData(),
dashboard,
dashboard: dashboard || undefined,
numCols: !numCols ? undefined : numCols < 5 ? numCols : 4,
},
};

View File

@ -33,7 +33,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
props: {
// reduntant: page component doesn't do graphql requests, but it's still nice/more consistent to have data in cache
urqlState: ssrCache.extractData(),
dashboard,
dashboard: dashboard || undefined,
},
};
};

View File

@ -65,6 +65,7 @@ const SecretEmbedPage: NextPage<Props> = ({ results }) => {
question={result}
showTimeStamp={true}
expandFooterToFullWidth={true}
showExpandButton={false}
/>
) : null}
</div>

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 '../fragments.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: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: 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, fetched: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: 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: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: 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, fetched: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } }> } | 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: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: 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, fetched: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: 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

@ -1,9 +1,9 @@
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: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } };
export type QuestionFragment = { __typename?: 'Question', id: string, url: string, title: string, description: string, fetched: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } };
export type QuestionWithHistoryFragment = { __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, history: Array<{ __typename?: 'History', timestamp: number, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } };
export type QuestionWithHistoryFragment = { __typename?: 'Question', id: string, url: string, title: string, description: string, fetched: number, visualization?: string | null, history: Array<{ __typename?: 'History', fetched: number, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: 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":"numForecasters"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"spread"}},{"kind":"Field","name":{"kind":"Name","value":"sharesVolume"}},{"kind":"Field","name":{"kind":"Name","value":"openInterest"}},{"kind":"Field","name":{"kind":"Name","value":"liquidity"}},{"kind":"Field","name":{"kind":"Name","value":"tradeVolume"}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualization"}}]}}]} as unknown as DocumentNode<QuestionFragment, unknown>;
export const QuestionWithHistoryFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"QuestionWithHistory"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Question"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Question"}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"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"}}]}}]}}]}},...QuestionFragmentDoc.definitions]} as unknown as DocumentNode<QuestionWithHistoryFragment, unknown>;
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":"fetched"}},{"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":"numForecasters"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"spread"}},{"kind":"Field","name":{"kind":"Name","value":"sharesVolume"}},{"kind":"Field","name":{"kind":"Name","value":"openInterest"}},{"kind":"Field","name":{"kind":"Name","value":"liquidity"}},{"kind":"Field","name":{"kind":"Name","value":"tradeVolume"}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualization"}}]}}]} as unknown as DocumentNode<QuestionFragment, unknown>;
export const QuestionWithHistoryFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"QuestionWithHistory"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Question"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Question"}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fetched"}},{"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"}}]}}]}}]}},...QuestionFragmentDoc.definitions]} as unknown as DocumentNode<QuestionWithHistoryFragment, unknown>;

View File

@ -3,7 +3,7 @@ fragment Question on Question {
url
title
description
timestamp
fetched
options {
name
probability
@ -29,7 +29,7 @@ fragment Question on Question {
fragment QuestionWithHistory on Question {
...Question
history {
timestamp
fetched
options {
name
probability

View File

@ -58,16 +58,16 @@ export const buildChartData = (
let seriesList: ChartSeries[] = [...Array(seriesNames.length)].map((x) => []);
const sortedHistory = question.history.sort((a, b) =>
a.timestamp < b.timestamp ? -1 : 1
a.fetched < b.fetched ? -1 : 1
);
{
let previousDate = -Infinity;
for (const item of sortedHistory) {
if (item.timestamp - previousDate < 12 * 60 * 60) {
if (item.fetched - previousDate < 12 * 60 * 60) {
continue;
}
const date = new Date(item.timestamp * 1000);
const date = new Date(item.fetched * 1000);
for (const option of item.options) {
if (option.name == null || option.probability == null) {
@ -84,7 +84,7 @@ export const buildChartData = (
};
seriesList[idx].push(result);
}
previousDate = item.timestamp;
previousDate = item.fetched;
}
}
@ -96,12 +96,12 @@ export const buildChartData = (
}
const minDate = sortedHistory.length
? startOfDay(new Date(sortedHistory[0].timestamp * 1000))
? startOfDay(new Date(sortedHistory[0].fetched * 1000))
: startOfToday();
const maxDate = sortedHistory.length
? addDays(
startOfDay(
new Date(sortedHistory[sortedHistory.length - 1].timestamp * 1000)
new Date(sortedHistory[sortedHistory.length - 1].fetched * 1000)
),
1
)

View File

@ -5,7 +5,7 @@ import ReactMarkdown from "react-markdown";
import { Card } from "../../../common/Card";
import { CopyText } from "../../../common/CopyText";
import { QuestionFragment } from "../../../fragments.generated";
import { cleanText } from "../../../utils";
import { cleanText, isQuestionBinary } from "../../../utils";
import { QuestionOptions } from "../QuestionOptions";
import { QuestionFooter } from "./QuestionFooter";
@ -78,11 +78,9 @@ export const QuestionCard: React.FC<Props> = ({
showExpandButton = true,
}) => {
const { options } = question;
const lastUpdated = new Date(question.timestamp * 1000);
const lastUpdated = new Date(question.fetched * 1000);
const isBinary =
options.length === 2 &&
(options[0].name === "Yes" || options[0].name === "No");
const isBinary = isQuestionBinary(question);
return (
<Card>
@ -114,31 +112,12 @@ export const QuestionCard: React.FC<Props> = ({
</a>
</Card.Title>
</div>
{isBinary ? (
<div className="flex justify-between">
<QuestionOptions
question={question}
maxNumOptions={5}
optionTextSize={"text-normal"}
onlyFirstEstimate={false}
/>
<div className={isBinary ? "flex justify-between" : "space-y-4"}>
<QuestionOptions question={question} maxNumOptions={5} />
<div className={`hidden ${showTimeStamp ? "sm:block" : ""}`}>
<LastUpdated timestamp={lastUpdated} />
</div>
</div>
) : (
<div className="space-y-2">
<QuestionOptions
question={question}
maxNumOptions={5}
optionTextSize={"text-sm"}
onlyFirstEstimate={false}
/>
<div className={`hidden ${showTimeStamp ? "sm:block" : ""} ml-6`}>
<LastUpdated timestamp={lastUpdated} />
</div>
</div>
)}
{question.platform.id !== "guesstimate" && options.length < 3 && (
<div className="text-gray-500">

View File

@ -1,7 +1,4 @@
import {
FullQuestionOption,
isFullQuestionOption,
} from "../../../common/types";
import { FullQuestionOption, isFullQuestionOption } from "../../../common/types";
import { QuestionFragment } from "../../fragments.generated";
import { isQuestionBinary } from "../../utils";
import { formatProbability } from "../utils";
@ -62,6 +59,18 @@ const primaryForecastColor = (probability: number) => {
}
};
const chooseColor = (probability: number) => {
if (probability < 0.1) {
return "bg-blue-50 text-blue-500";
} else if (probability < 0.3) {
return "bg-blue-100 text-blue-600";
} else if (probability < 0.7) {
return "bg-blue-200 text-blue-700";
} else {
return "bg-blue-300 text-blue-800";
}
};
const primaryEstimateAsText = (probability: number) => {
if (probability < 0.03) {
return "Exceptionally unlikely";
@ -80,39 +89,38 @@ const primaryEstimateAsText = (probability: number) => {
}
};
const chooseColor = (probability: number) => {
if (probability < 0.1) {
return "bg-blue-50 text-blue-500";
} else if (probability < 0.3) {
return "bg-blue-100 text-blue-600";
} else if (probability < 0.7) {
return "bg-blue-200 text-blue-700";
} else {
return "bg-blue-300 text-blue-800";
}
type OptionProps = {
option: FullQuestionOption;
mode: "primary" | "normal"; // affects font size and colors
textMode: "name" | "probability"; // whether to output option name or probability estimate as text
};
const OptionRow: React.FC<{
option: FullQuestionOption;
optionTextSize: string;
}> = ({ option, optionTextSize }) => {
const OptionRow: React.FC<OptionProps> = ({ option, mode, textMode }) => {
return (
<div className="flex items-center">
<div className="flex items-center space-x-2">
<div
className={`${chooseColor(
option.probability
)} w-14 flex-none rounded-md py-0.5 ${
optionTextSize || "text-sm"
} text-center`}
className={`flex-none rounded-md text-center ${
mode === "primary"
? "text-normal text-white px-2 py-0.5 font-bold"
: "text-sm w-14 py-0.5"
} ${
mode === "primary"
? primaryForecastColor(option.probability)
: chooseColor(option.probability)
}`}
>
{formatProbability(option.probability)}
</div>
<div
className={`text-gray-700 pl-3 leading-snug ${
optionTextSize || "text-sm"
className={`leading-snug ${
mode === "primary" ? "text-normal" : "text-sm"
} ${
mode === "primary" ? textColor(option.probability) : "text-gray-700"
}`}
>
{option.name}
{textMode === "name"
? option.name
: primaryEstimateAsText(option.probability)}
</div>
</div>
);
@ -121,9 +129,8 @@ const OptionRow: React.FC<{
export const QuestionOptions: React.FC<{
question: QuestionFragment;
maxNumOptions: number;
optionTextSize: string;
onlyFirstEstimate: boolean;
}> = ({ question, maxNumOptions, optionTextSize, onlyFirstEstimate }) => {
forcePrimaryMode?: boolean;
}> = ({ question, maxNumOptions, forcePrimaryMode = false }) => {
const isBinary = isQuestionBinary(question);
if (isBinary) {
@ -134,59 +141,10 @@ export const QuestionOptions: React.FC<{
if (!isFullQuestionOption(yesOption)) {
return null; // missing data
}
return (
<div className="space-x-2">
<span
className={`${primaryForecastColor(
yesOption.probability
)} text-white w-16 rounded-md px-2 py-1 font-bold ${
optionTextSize || "text-normal"
}`}
>
{formatProbability(yesOption.probability)}
</span>
<span
className={`${textColor(yesOption.probability)} ${
optionTextSize || "text-normal"
} text-gray-500 inline-block`}
>
{primaryEstimateAsText(yesOption.probability)}
</span>
</div>
<OptionRow option={yesOption} mode="primary" textMode="probability" />
);
} else if (onlyFirstEstimate) {
if (question.options.length > 0) {
const yesOption =
question.options.length > 0 ? question.options[0] : null;
if (!yesOption) {
return null; // shouldn't happen
}
if (!isFullQuestionOption(yesOption)) {
return null; // missing data
}
return (
<div className="space-x-2">
<span
className={`${primaryForecastColor(
yesOption.probability
)} text-white w-16 rounded-md px-2 py-1 font-bold ${
optionTextSize || "text-normal"
}`}
>
{formatProbability(yesOption.probability)}
</span>
<span
className={`${textColor(yesOption.probability)} ${
optionTextSize || "text-normal"
} text-gray-500 inline-block`}
>
{yesOption.name}
</span>
</div>
);
} else {
return null;
}
} else {
const optionsSorted = question.options
.filter(isFullQuestionOption)
@ -197,7 +155,12 @@ export const QuestionOptions: React.FC<{
return (
<div className="space-y-2">
{optionsMaxN.map((option, i) => (
<OptionRow option={option} key={i} optionTextSize={optionTextSize} />
<OptionRow
key={i}
option={option}
mode={forcePrimaryMode ? "primary" : "normal"}
textMode="name"
/>
))}
</div>
);

View File

@ -54,9 +54,5 @@ function getStarsColor(numstars: number) {
}
export const Stars: React.FC<{ num: number }> = ({ num }) => {
return (
<div className={`self-center col-span-1 ${getStarsColor(num)}`}>
{getstars(num)}
</div>
);
return <div className={getStarsColor(num)}>{getstars(num)}</div>;
};

View File

@ -1,4 +1,5 @@
import { GetServerSideProps, NextPage } from "next";
import NextError from "next/error";
import { FaExternalLinkAlt } from "react-icons/fa";
import ReactMarkdown from "react-markdown";
@ -11,8 +12,8 @@ import { ssrUrql } from "../../urql";
import { CaptureQuestion } from "../components/CaptureQuestion";
import { HistoryChart } from "../components/HistoryChart";
import { IndicatorsTable } from "../components/IndicatorsTable";
import { Stars } from "../components/Stars";
import { QuestionOptions } from "../components/QuestionOptions";
import { Stars } from "../components/Stars";
import { QuestionPageDocument } from "../queries.generated";
interface Props {
@ -48,16 +49,22 @@ const Section: React.FC<{ title: string }> = ({ title, children }) => (
</div>
);
const PlatformLink: React.FC<{ question: QuestionWithHistoryFragment }> = ({
question,
}) => (
<a
className="px-2 py-1 border-2 border-gray-400 rounded-lg text-black no-underline text-normal hover:bg-gray-100 flex flex-nowrap space-x-1 items-center"
href={question.url}
target="_blank"
>
<span>{question.platform.label}</span>
<FaExternalLinkAlt className="text-gray-400 inline sm:text-md text-md" />
</a>
);
const LargeQuestionCard: React.FC<{
question: QuestionWithHistoryFragment;
}> = ({ question }) => {
let probabilities = question.options;
let optionsOrderedByProbability = question.options.sort((a, b) =>
(a.probability || 0) > (b.probability || 0) ? -1 : 1
);
let optionWithHighestProbability =
question.options.length > 0 ? [optionsOrderedByProbability[0]] : [];
return (
<Card highlightOnHover={false} large={true}>
<h1 className="sm:text-3xl text-xl">
@ -66,28 +73,18 @@ const LargeQuestionCard: React.FC<{
href={question.url}
target="_blank"
>
{question.title}{" "}
{question.title}
</a>
</h1>
<div className="flex gap-2 mb-5 mt-5">
<a
className="text-black no-underline border-2 rounded-lg border-gray-400 rounded p-1 px-2 text-normal hover:text-gray-600"
href={question.url}
target="_blank"
>
{question.platform.label}{" "}
<FaExternalLinkAlt className="text-gray-400 inline sm:text-md text-md mb-1" />
</a>
<div className="flex items-center gap-2 mb-5 mt-5">
<PlatformLink question={question} />
<Stars num={question.qualityIndicators.stars} />
<span className="border-2 border-white p-1 px-2 ">
<QuestionOptions
question={{ ...question }}
maxNumOptions={1}
optionTextSize={"text-normal"}
onlyFirstEstimate={true}
forcePrimaryMode={true}
/>
</span>
</div>
<div className="mb-10">
@ -143,7 +140,13 @@ const QuestionPage: NextPage<Props> = ({ id }) => {
<Layout page="question">
<div className="max-w-4xl mx-auto">
<Query document={QuestionPageDocument} variables={{ id }}>
{({ data }) => <QuestionScreen question={data.result} />}
{({ data }) =>
data.result ? (
<QuestionScreen question={data.result} />
) : (
<NextError statusCode={404} />
)
}
</Query>
</div>
</Layout>

View File

@ -7,7 +7,7 @@ export type QuestionPageQueryVariables = Types.Exact<{
}>;
export type QuestionPageQuery = { __typename?: 'Query', result: { __typename?: 'Question', id: string, url: string, title: string, description: string, timestamp: number, visualization?: string | null, history: Array<{ __typename?: 'History', timestamp: number, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } } };
export type QuestionPageQuery = { __typename?: 'Query', result?: { __typename?: 'Question', id: string, url: string, title: string, description: string, fetched: number, visualization?: string | null, history: Array<{ __typename?: 'History', fetched: number, options: Array<{ __typename?: 'ProbabilityOption', name?: string | null, probability?: number | 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } } | null };
export const QuestionPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"QuestionPage"},"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":"question"},"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":"QuestionWithHistory"}}]}}]}},...QuestionWithHistoryFragmentDoc.definitions]} as unknown as DocumentNode<QuestionPageQuery, QuestionPageQueryVariables>;

View File

@ -5,14 +5,14 @@ import { QuestionFragmentDoc } from '../fragments.generated';
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: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } }> };
export type FrontpageQuery = { __typename?: 'Query', result: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, fetched: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: 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: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } }> };
export type SearchQuery = { __typename?: 'Query', result: Array<{ __typename?: 'Question', id: string, url: string, title: string, description: string, fetched: 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, numForecasters?: number | null, volume?: number | null, spread?: number | null, sharesVolume?: number | null, openInterest?: number | null, liquidity?: number | null, tradeVolume?: number | null } }> };
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>;