Skip to content

Commit

Permalink
Merge pull request #15 from canvas/jsb/react-input-fix
Browse files Browse the repository at this point in the history
Cache initial filter options from backend, BigNumber date aggregation fix, selected styling
  • Loading branch information
JeremyBernier authored Jan 8, 2024
2 parents 390041e + ed22657 commit 6d9a302
Show file tree
Hide file tree
Showing 32 changed files with 149 additions and 208 deletions.
4 changes: 2 additions & 2 deletions react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions react/src/CanvasElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export const CanvasElement = ({
if (elementType.type === 'component') {
if (elementType.component.component === 'BigNumber') {
return (
<Element key={elementId} title={title || ''} elementId={elementId}>
<BigNumber element={element as ComponentEmbedElement} />
<Element key={elementId} elementId={elementId}>
<BigNumber element={element as ComponentEmbedElement} title={title || ''} />
</Element>
);
}
Expand Down
2 changes: 1 addition & 1 deletion react/src/Element.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

type ElementProps = {
title: string;
title?: string;
children: React.ReactNode;
elementId: string;
};
Expand Down
1 change: 0 additions & 1 deletion react/src/Spreadsheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { VariableSizeGrid as Grid } from 'react-window';
import _ from 'lodash';
// import { usePrevious } from '../../v1/util/UtilUtils';
import AutoSizer from 'react-virtualized-auto-sizer';
// import { formatCell, isCohortStore } from '../../util/StoreUtil';
import percentile from 'percentile';
import { DateTime } from 'luxon';
import { getTypeIcon } from './icons/ColumnTypeUtil';
Expand Down
49 changes: 46 additions & 3 deletions react/src/components/BigNumber.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,51 @@
import React from 'react';
import { ComponentEmbedElement } from '../types';
import { parseNumber } from '../Spreadsheet';
import { DownToTheRightIcon, UpToTheRightIcon } from '../icons';

export const BigNumber = ({ element }: { element: ComponentEmbedElement }) => {
const formattedValue = element.elementType.formattedValues?.[1];
export const BigNumber = ({ element, title }: { element: ComponentEmbedElement; title: string }) => {
const data = element.elementType.data;

return <> {formattedValue != null && <div className="text-3xl">{formattedValue}</div>}</>;
const currentNumber = data && data[0] && data[0][0] ? data[0][0] : null;
const lastNumber = data && data[0] && data[0][1] ? data[0][1] : null;

let change: string | null = null;
let changeIcon: any = null;
if (currentNumber !== null && lastNumber !== null) {
const last = parseNumber(lastNumber);
const current = parseNumber(currentNumber);
if (last != 0) {
const over = (current - last) / last;
change = (Math.abs(over) * 100.0).toLocaleString(undefined, {
maximumFractionDigits: 1,
});

if (over > 0) {
changeIcon = <UpToTheRightIcon className="h-3" />;
} else if (over < 0) {
changeIcon = <DownToTheRightIcon className="h-3" />;
} else {
changeIcon = null;
}
}
}

return (
<div>
{title && (
<div className="flex gap-x-4 items-center mb-1">
<div className="text-[15px] font-medium text-default/80">{title}</div>
{change ? (
<div className="flex items-center gap-1">
{changeIcon} {change}%
</div>
) : (
<></>
)}
</div>
)}
<span className="font-big-number text-3xl">{currentNumber} </span>
{lastNumber ? <span className="text-[15px] text-faded">from {lastNumber}</span> : null}
</div>
);
};
37 changes: 21 additions & 16 deletions react/src/components/MultiSelectInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, Fragment } from 'react';
import React, { Fragment } from 'react';
import { Listbox, Transition } from '@headlessui/react';
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';

Expand All @@ -9,13 +9,13 @@ export type SelectOption = [Value, Label];
type MultiSelectInputProps = {
value: string; // value === '' when default option is selected
onChange: (value: string) => void;
options: SelectOption[];
options: SelectOption[]; // [ID, label]
defaultOption?: string;
};

function getLabel(item: SelectOption) {
if (item[0] === '') return item[1];
return `${item[0]}${item[1] ? ` (${item[1]})` : ''}`;
return `${item[1]}${item[0] ? ` (${item[0]})` : ''}`;
}

// taken from https://headlessui.com/react/listbox
Expand Down Expand Up @@ -49,20 +49,25 @@ const MultiSelectInputDisplay = ({ value, onChange, options }: MultiSelectInputP
}
value={option}
>
{({ selected }) => (
<>
<span
className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}
>
{getLabel(option)}
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600">
<CheckIcon className="h-5 w-5" aria-hidden="true" />
{() => {
const selected = option[0] === value;
return (
<>
<span
className={`block truncate ${
selected ? 'font-medium' : 'font-normal'
}`}
>
{getLabel(option)}
</span>
) : null}
</>
)}
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600">
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</>
);
}}
</Listbox.Option>
))}
</Listbox.Options>
Expand Down
54 changes: 54 additions & 0 deletions react/src/filter/Filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import isEmpty from 'lodash/isEmpty';
import MultiSelectInput from '../components/MultiSelectInput';
import useCanvasState from '../state/useCanvasState';
import { GetCanvasEmbedResponse } from '@/src/rust_types/GetCanvasEmbedResponse';
import { FilterConfig } from '../rust_types/FilterConfig';

const Filter = ({ canvasData, filter }: { canvasData: GetCanvasEmbedResponse; filter: FilterConfig }) => {
const updateFilter = useCanvasState((state) => state.updateFilter);
const selectedFilters = useCanvasState((state) => state.filters);
const valueIsSelected = !isEmpty(selectedFilters);
// Cache mapping filterId to its filterOptions
// We are exclusively using the initial filter options from the initial API response
const [filterOptionsCache, setFilterOptionsCache] = React.useState<Record<string, string[][]>>({});

// currently only Select filters are supported, but remove this as we support more filter types
if (filter.filterType.type !== 'select' || filter.filterType.storeId == null) {
return null;
}

let filterOptions;

if (filter.filterId in filterOptionsCache) {
filterOptions = filterOptionsCache[filter.filterId];
} else {
filterOptions = canvasData.filters.uniqueValues[filter.filterType.storeId];
setFilterOptionsCache((prev) => ({ ...prev, [filter.filterId]: filterOptions }));
}

return (
<div key={filter.filterId} className="flex gap-3">
<MultiSelectInput
value={selectedFilters[filter.variable]}
onChange={(item: string) => {
if (item === '' || item == null) {
updateFilter({});
return;
}
const variable = filter.variable;
updateFilter({ [variable]: item });
}}
defaultOption="Select Filter"
options={filterOptions}
/>
{valueIsSelected && (
<button onClick={() => updateFilter({})} className="text-xs text-blue-700">
Clear Filter
</button>
)}
</div>
);
};

export default Filter;
35 changes: 5 additions & 30 deletions react/src/filter/Filters.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,16 @@
import React from 'react';
import isEmpty from 'lodash/isEmpty';
import MultiSelectInput from '../components/MultiSelectInput';
import useCanvasState from '../state/useCanvasState';
import { GetCanvasEmbedResponse } from '@/src/rust_types/GetCanvasEmbedResponse';
import Filter from './Filter';

export function Filters({ canvasData }: { canvasData: GetCanvasEmbedResponse }) {
const filters = canvasData?.filters?.filters;
const filtersVisible = filters?.filter((filter) => filter?.filterType?.type === 'select');
const updateFilter = useCanvasState((state) => state.updateFilter);
const selectedFilters = useCanvasState((state) => state.filters);
const valueIsSelected = !isEmpty(selectedFilters);
const filtersVisible = filters?.filter(
(filter) => filter?.filterType?.type === 'select' && filter.filterType.storeId != null,
);

return (
<section>
{filtersVisible?.map((filter) => (
<div key={filter.filterId} className="flex gap-3">
<MultiSelectInput
value={selectedFilters[filter.variable]}
onChange={(item: string) => {
if (item === '' || item == null) {
updateFilter({});
return;
}
const variable = filter.variable;
updateFilter({ [variable]: item });
}}
defaultOption="Select Filter"
// @ts-ignore
options={canvasData.filters.uniqueValues[filter.filterType.storeId]}
/>
{valueIsSelected && (
<button onClick={() => updateFilter({})} className="text-xs text-blue-700">
Clear Filter
</button>
)}
</div>
))}
{filtersVisible?.map((filter) => <Filter key={filter.filterId} canvasData={canvasData} filter={filter} />)}
</section>
);
}
2 changes: 2 additions & 0 deletions react/src/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// export { Icon };

export { default as IntegerIcon } from '../../static/icons/component/integer_icon.svg';
export { default as UpToTheRightIcon } from '../../static/icons/up-to-the-right.svg';
export { default as DownToTheRightIcon } from '../../static/icons/down-to-the-right.svg';

// export const AddSourceIcon = icon('./static/icons/nav/add_source.svg');
// export const CheckmarkIcon = icon('./static/icons/nav/checkmark.svg');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { TextValue } from './TextValue';

export interface Field {
id: string;
label: string;
isDate: boolean;
export interface Cell {
value: TextValue;
}
4 changes: 0 additions & 4 deletions react/src/rust_types/ChartAggregation.ts

This file was deleted.

2 changes: 1 addition & 1 deletion react/src/rust_types/ComponentEmbed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import type { Component } from './Component';

export interface ComponentEmbed {
component: Component;
formattedValues: Array<string>;
data: Array<Array<string>>;
}
7 changes: 0 additions & 7 deletions react/src/rust_types/CustomFilterSlice.ts

This file was deleted.

10 changes: 0 additions & 10 deletions react/src/rust_types/DataStorePayload.ts

This file was deleted.

3 changes: 0 additions & 3 deletions react/src/rust_types/DataStoreState.ts

This file was deleted.

28 changes: 0 additions & 28 deletions react/src/rust_types/FilterByConditionSlice.ts

This file was deleted.

6 changes: 0 additions & 6 deletions react/src/rust_types/GenerateChartDataResponse.ts

This file was deleted.

6 changes: 0 additions & 6 deletions react/src/rust_types/GenerateChartTemplateBody.ts

This file was deleted.

7 changes: 0 additions & 7 deletions react/src/rust_types/GetChartDataBody.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type ActionKind = 'Append' | 'Set' | 'SetMeta';
export interface Grid<T> {
data: Array<T>;
row_count: number;
column_count: number;
}
9 changes: 0 additions & 9 deletions react/src/rust_types/RuleFilterSlice.ts

This file was deleted.

8 changes: 0 additions & 8 deletions react/src/rust_types/RuleSlice.ts

This file was deleted.

Loading

0 comments on commit 6d9a302

Please sign in to comment.