Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format currencies improvements #648

Merged
merged 5 commits into from
Aug 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions app/components/debug-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ const Search = ({

useEffect(() => {
startTimeRef.current = Date.now();
}, [query, locale]);
}, [query, locale, includeDrafts]);

const [cubes] = useDataCubesQuery({
variables: {
locale: locale,
query: query,
filters: filters.map(({ name, type, value }) => ({ type, value })),
filters: filters.map(({ type, value }) => ({ type, value })),
includeDrafts,
sourceUrl,
sourceType: "sparql",
Expand All @@ -69,7 +69,6 @@ const Search = ({

useEffect(() => {
if (cubes.data) {
console.log("seting end time", query);
setEndTime(Date.now());
}
}, [cubes.data]);
Expand Down
13 changes: 13 additions & 0 deletions app/configurator/components/ui-helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ describe("useDimensionFormatters", () => {
isNumerical: true,
isKeyDimension: false,
} as DimensionMetaDataFragment,
{
iri: "iri-currency",
isNumerical: true,
isKeyDimension: false,
isCurrency: true,
currencyExponent: 1,
__typename: "Measure",
} as DimensionMetaDataFragment,
])
);
return { formatters };
Expand All @@ -75,6 +83,11 @@ describe("useDimensionFormatters", () => {
const { formatters } = setup();
expect(formatters["iri-number"]("2.33333")).toEqual("2,33");
});

it("should work with currencies", () => {
const { formatters } = setup();
expect(formatters["iri-currency"]("20002.3333")).toEqual("20'002,3");
});
ptbrowne marked this conversation as resolved.
Show resolved Hide resolved
});

describe("time intervals", () => {
Expand Down
40 changes: 30 additions & 10 deletions app/configurator/components/ui-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { ChartProps } from "../../charts/shared/use-chart-state";
import { Observation } from "../../domain/data";
import {
DimensionMetaDataFragment,
Measure,
TemporalDimension,
TimeUnit,
} from "../../graphql/query-hooks";
Expand Down Expand Up @@ -149,9 +150,16 @@ const namedNodeFormatter = (d: DimensionMetaDataFragment) => {
};
};

const currencyFormatter = (d: Measure, locale: string) => {
const formatLocale = getD3FormatLocale(locale);
// Use the currency exponent from the dimension, with default 2
return formatLocale.format(`,.${d.currencyExponent || 2}f`);
};

export const useDimensionFormatters = (
dimensions: DimensionMetaDataFragment[]
) => {
const locale = useLocale();
const formatNumber = useFormatNumber() as unknown as (
d: number | string
) => string;
Expand All @@ -161,16 +169,28 @@ export const useDimensionFormatters = (
return useMemo(() => {
return Object.fromEntries(
dimensions.map((d) => {
return [
d.iri,
d.__typename === "Measure" || d.isNumerical
? formatNumber
: d.__typename === "TemporalDimension"
? dateFormatterFromDimension(d, dateFormatters, formatDateAuto)
: isNamedNodeDimension(d)
? namedNodeFormatter(d)
: formatIdentity,
];
let formatter: (s: any) => string;
if (d.__typename === "Measure") {
if (d.isCurrency) {
formatter = currencyFormatter(d, locale);
} else {
formatter = formatNumber;
}
} else if (d.__typename === "TemporalDimension") {
formatter = dateFormatterFromDimension(
d,
dateFormatters,
formatDateAuto
);
} else if (isNamedNodeDimension(d)) {
formatter = namedNodeFormatter(d);
} else if (d.isNumerical) {
formatter = formatNumber;
} else {
formatter = formatIdentity;
}

return [d.iri, formatter];
})
);
}, [dimensions, formatNumber, dateFormatters, formatDateAuto]);
Expand Down
4 changes: 4 additions & 0 deletions app/graphql/queries/data-cubes.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ fragment dimensionMetaData on Dimension {
timeUnit
timeFormat
}
... on Measure {
isCurrency
currencyExponent
}
}

query DataCubePreview(
Expand Down
8 changes: 7 additions & 1 deletion app/graphql/query-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ export type Measure = Dimension & {
order?: Maybe<Scalars['Int']>;
isNumerical: Scalars['Boolean'];
isKeyDimension: Scalars['Boolean'];
isCurrency?: Maybe<Scalars['Boolean']>;
currencyExponent?: Maybe<Scalars['Int']>;
values: Array<Scalars['DimensionValue']>;
related?: Maybe<Array<RelatedDimension>>;
hierarchy?: Maybe<Array<HierarchyValue>>;
Expand Down Expand Up @@ -453,7 +455,7 @@ type DimensionMetaData_GeoCoordinatesDimension_Fragment = { __typename: 'GeoCoor

type DimensionMetaData_GeoShapesDimension_Fragment = { __typename: 'GeoShapesDimension', iri: string, label: string, isNumerical: boolean, isKeyDimension: boolean, order?: Maybe<number>, values: Array<any>, unit?: Maybe<string>, related?: Maybe<Array<{ __typename: 'RelatedDimension', iri: string, type: string }>> };

type DimensionMetaData_Measure_Fragment = { __typename: 'Measure', iri: string, label: string, isNumerical: boolean, isKeyDimension: boolean, order?: Maybe<number>, values: Array<any>, unit?: Maybe<string>, related?: Maybe<Array<{ __typename: 'RelatedDimension', iri: string, type: string }>> };
type DimensionMetaData_Measure_Fragment = { __typename: 'Measure', isCurrency?: Maybe<boolean>, currencyExponent?: Maybe<number>, iri: string, label: string, isNumerical: boolean, isKeyDimension: boolean, order?: Maybe<number>, values: Array<any>, unit?: Maybe<string>, related?: Maybe<Array<{ __typename: 'RelatedDimension', iri: string, type: string }>> };

type DimensionMetaData_NominalDimension_Fragment = { __typename: 'NominalDimension', iri: string, label: string, isNumerical: boolean, isKeyDimension: boolean, order?: Maybe<number>, values: Array<any>, unit?: Maybe<string>, related?: Maybe<Array<{ __typename: 'RelatedDimension', iri: string, type: string }>> };

Expand Down Expand Up @@ -810,6 +812,10 @@ export const DimensionMetaDataFragmentDoc = gql`
timeUnit
timeFormat
}
... on Measure {
isCurrency
currencyExponent
}
}
`;
export const HierarchyValueFieldsFragmentDoc = gql`
Expand Down
4 changes: 4 additions & 0 deletions app/graphql/resolver-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ export type Measure = Dimension & {
order?: Maybe<Scalars['Int']>;
isNumerical: Scalars['Boolean'];
isKeyDimension: Scalars['Boolean'];
isCurrency?: Maybe<Scalars['Boolean']>;
currencyExponent?: Maybe<Scalars['Int']>;
values: Array<Scalars['DimensionValue']>;
related?: Maybe<Array<RelatedDimension>>;
hierarchy?: Maybe<Array<HierarchyValue>>;
Expand Down Expand Up @@ -710,6 +712,8 @@ export type MeasureResolvers<ContextType = GraphQLContext, ParentType extends Re
order?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
isNumerical?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
isKeyDimension?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
isCurrency?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
currencyExponent?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
values?: Resolver<Array<ResolversTypes['DimensionValue']>, ParentType, ContextType, RequireFields<MeasureValuesArgs, 'sourceType' | 'sourceUrl'>>;
related?: Resolver<Maybe<Array<ResolversTypes['RelatedDimension']>>, ParentType, ContextType>;
hierarchy?: Resolver<Maybe<Array<ResolversTypes['HierarchyValue']>>, ParentType, ContextType, RequireFields<MeasureHierarchyArgs, 'sourceType' | 'sourceUrl'>>;
Expand Down
25 changes: 17 additions & 8 deletions app/graphql/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const DataCube: DataCubeResolvers = {
},
};

const mkDimensionResolvers = (debugName: string): Resolvers["Dimension"] => ({
const mkDimensionResolvers = (type: string): Resolvers["Dimension"] => ({
// TODO: how to pass dataSource here? If it's possible, then we also could have
// different resolvers for RDF and SQL.
__resolveType({ data: { dataKind, scaleType } }) {
Expand Down Expand Up @@ -160,7 +160,10 @@ export const resolvers: Resolvers = {
getSparqlEditorUrl({ query }),
},
Dimension: {
__resolveType({ data: { dataKind, scaleType } }) {
__resolveType({ data: { dataKind, scaleType, isMeasureDimension } }) {
if (isMeasureDimension) {
return "Measure";
}
if (dataKind === "Time") {
return "TemporalDimension";
} else if (dataKind === "GeoCoordinates") {
Expand All @@ -175,25 +178,25 @@ export const resolvers: Resolvers = {
},
},
NominalDimension: {
...mkDimensionResolvers("nominal"),
...mkDimensionResolvers("NominalDimension"),
},
OrdinalDimension: {
...mkDimensionResolvers("ordinal"),
...mkDimensionResolvers("OrdinalDimension"),
},
TemporalDimension: {
...mkDimensionResolvers("temporal"),
...mkDimensionResolvers("TemporalDimension"),
timeUnit: ({ data: { timeUnit } }) => timeUnit!,
timeFormat: ({ data: { timeFormat } }) => timeFormat!,
},
GeoCoordinatesDimension: {
...mkDimensionResolvers("geocoordinates"),
...mkDimensionResolvers("GeoCoordinatesDimension"),
geoCoordinates: async (parent, args, { setup }, info) => {
const { loaders } = await setup(info);
return await loaders.geoCoordinates.load(parent);
},
},
GeoShapesDimension: {
...mkDimensionResolvers("geoshapes"),
...mkDimensionResolvers("GeoShapesDimension"),
geoShapes: async (parent, args, { setup }, info) => {
const { loaders } = await setup(info);
const dimValues = (await loaders.dimensionValues.load(
Expand Down Expand Up @@ -229,6 +232,12 @@ export const resolvers: Resolvers = {
},
},
Measure: {
...mkDimensionResolvers("measure"),
...mkDimensionResolvers("Measure"),
isCurrency: ({ data: { isCurrency } }) => {
return isCurrency;
},
currencyExponent: ({ data: { currencyExponent } }) => {
return currencyExponent || 0;
},
},
};
4 changes: 4 additions & 0 deletions app/graphql/resolvers/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ const parseSQLDimension = (
iri,
name,
isLiteral: true,

// FIXME: Handle currencies in SQL resolvers
isCurrency: false,
currencyExponent: 0,
// FIXME: not only measures can be numerical
isNumerical: isMeasure,
isKeyDimension: true,
Expand Down
2 changes: 2 additions & 0 deletions app/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ type Measure implements Dimension {
order: Int
isNumerical: Boolean!
isKeyDimension: Boolean!
isCurrency: Boolean
currencyExponent: Int
values(
sourceType: String!
sourceUrl: String!
Expand Down
2 changes: 2 additions & 0 deletions app/graphql/shared-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export type ResolvedDimension = {
isNumerical: boolean;
isKeyDimension: boolean;
isMeasureDimension: boolean;
isCurrency: boolean;
currencyExponent?: number;
hasUndefinedValues: boolean;
unit?: string;
dataType?: string;
Expand Down
16 changes: 11 additions & 5 deletions app/rdf/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,10 @@ export const parseCubeDimension = ({
dim: CubeDimension;
cube: Cube;
locale: string;
units?: Map<string, { iri: Term; label?: Term }>;
units?: Map<
string,
{ iri: Term; label?: Term; isCurrency?: Term; currencyExponent?: Term }
>;
}): ResolvedDimension => {
const outOpts = { language: getQueryLocales(locale) };

Expand Down Expand Up @@ -227,9 +230,8 @@ export const parseCubeDimension = ({
.terms.some((t) => t.equals(ns.cube.MeasureDimension));

const unitTerm = dim.out(ns.qudt.unit).term;
const dimensionUnit = unitTerm
? units?.get(unitTerm.value)?.label?.value
: undefined;
const dimensionUnit = unitTerm ? units?.get(unitTerm.value) : undefined;
const dimensionUnitLabel = dimensionUnit?.label?.value;

const rawOrder = dim.out(ns.sh.order).value;
const order = rawOrder !== undefined ? parseInt(rawOrder, 10) : undefined;
Expand All @@ -247,8 +249,12 @@ export const parseCubeDimension = ({
isKeyDimension,
isMeasureDimension,
hasUndefinedValues,
unit: dimensionUnit,
unit: dimensionUnitLabel,
dataType: dataType?.value,
isCurrency: !!dimensionUnit?.isCurrency?.value,
currencyExponent: dimensionUnit?.currencyExponent?.value
? parseInt(dimensionUnit?.currencyExponent?.value)
: undefined,
name: dim.out(ns.schema.name, outOpts).value ?? dim.path?.value!,
order: order,
dataKind: dataKindTerm?.equals(ns.time.GeneralDateTimeDescription)
Expand Down
8 changes: 4 additions & 4 deletions app/rdf/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { loadDimensionValues } from "./query-dimension-values";
import { loadResourceLabels } from "./query-labels";
import { loadResourcePositions } from "./query-positions";
import { loadUnversionedResources } from "./query-sameas";
import { loadUnitLabels } from "./query-unit-labels";
import { loadUnits } from "./query-unit-labels";

const DIMENSION_VALUE_UNDEFINED = ns.cube.Undefined.value;

Expand Down Expand Up @@ -173,8 +173,8 @@ export const getCubeDimensions = async ({
return t ? [t] : [];
});

const dimensionUnitLabels = index(
await loadUnitLabels({
const dimensionUnitIndex = index(
await loadUnits({
ids: dimensionUnits,
locale: "en", // No other locales exist yet
sparqlClient,
Expand All @@ -187,7 +187,7 @@ export const getCubeDimensions = async ({
dim,
cube,
locale,
units: dimensionUnitLabels,
units: dimensionUnitIndex,
});
});
} catch (e) {
Expand Down
12 changes: 8 additions & 4 deletions app/rdf/query-unit-labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ interface ResourceLabel {
label?: Term;
}

const buildUnitLabelsQuery = (values: Term[], locale: string) => {
return SELECT.DISTINCT`?iri ?label`.WHERE`
const buildUnitsQuery = (values: Term[], locale: string) => {
return SELECT.DISTINCT`?iri ?label ?isCurrency ?currencyExponent ?isCurrency`
.WHERE`
values ?iri {
${values}
}
Expand All @@ -21,6 +22,9 @@ const buildUnitLabelsQuery = (values: Term[], locale: string) => {
OPTIONAL { ?iri ${ns.qudt.ucumCode} ?ucumCode }
OPTIONAL { ?iri ${ns.qudt.expression} ?expression }

OPTIONAL { ?iri ?isCurrency ${ns.qudt.CurrencyUnit} }
OPTIONAL { ?iri ${ns.qudt.currencyExponent} ?currencyExponent }

BIND(str(coalesce(str(?symbol), str(?ucumCode), str(?expression), str(?rdfsLabel), "?")) AS ?label)

FILTER ( lang(?rdfsLabel) = "${locale}" )
Expand All @@ -30,7 +34,7 @@ const buildUnitLabelsQuery = (values: Term[], locale: string) => {
/**
* Load labels for a list of unit IDs
*/
export async function loadUnitLabels({
export async function loadUnits({
ids,
locale = "en",
sparqlClient,
Expand All @@ -42,6 +46,6 @@ export async function loadUnitLabels({
return batchLoad({
ids,
sparqlClient,
buildQuery: (values: Term[]) => buildUnitLabelsQuery(values, locale),
buildQuery: (values: Term[]) => buildUnitsQuery(values, locale),
});
}