Browse Source

Merge pull request #256 from nameczz/dev

Add time travel in vector search. Milvus is not support it in query yet.
nameczz 3 years ago
parent
commit
88be0b6b89

+ 3 - 1
client/package.json

@@ -6,10 +6,12 @@
   "bugs": "https://github.com/milvus-io/milvus-insight/issues",
   "bugs": "https://github.com/milvus-io/milvus-insight/issues",
   "private": true,
   "private": true,
   "dependencies": {
   "dependencies": {
+    "@date-io/dayjs": "1.x",
     "@loadable/component": "^5.15.0",
     "@loadable/component": "^5.15.0",
     "@material-ui/core": "4.11.4",
     "@material-ui/core": "4.11.4",
     "@material-ui/icons": "^4.11.2",
     "@material-ui/icons": "^4.11.2",
     "@material-ui/lab": "4.0.0-alpha.58",
     "@material-ui/lab": "4.0.0-alpha.58",
+    "@material-ui/pickers": "^3.3.10",
     "@mui/x-data-grid": "^4.0.0",
     "@mui/x-data-grid": "^4.0.0",
     "@testing-library/jest-dom": "^5.11.4",
     "@testing-library/jest-dom": "^5.11.4",
     "@testing-library/react": "^11.1.0",
     "@testing-library/react": "^11.1.0",
@@ -78,4 +80,4 @@
     "@types/webpack-env": "^1.16.3",
     "@types/webpack-env": "^1.16.3",
     "prettier": "2.3.2"
     "prettier": "2.3.2"
   }
   }
-}
+}

+ 5 - 1
client/src/App.tsx

@@ -1,3 +1,5 @@
+import { MuiPickersUtilsProvider } from '@material-ui/pickers';
+import DayjsUtils from '@date-io/dayjs';
 import Router from './router/Router';
 import Router from './router/Router';
 import { RootProvider } from './context/Root';
 import { RootProvider } from './context/Root';
 import { NavProvider } from './context/Navigation';
 import { NavProvider } from './context/Navigation';
@@ -10,7 +12,9 @@ function App() {
       <AuthProvider>
       <AuthProvider>
         <WebSocketProvider>
         <WebSocketProvider>
           <NavProvider>
           <NavProvider>
-            <Router></Router>
+            <MuiPickersUtilsProvider utils={DayjsUtils}>
+              <Router></Router>
+            </MuiPickersUtilsProvider>
           </NavProvider>
           </NavProvider>
         </WebSocketProvider>
         </WebSocketProvider>
       </AuthProvider>
       </AuthProvider>

+ 77 - 0
client/src/components/customDatePicker/CustomDatePicker.tsx

@@ -0,0 +1,77 @@
+import { FC, useState } from 'react';
+import { DateTimePicker } from '@material-ui/pickers';
+import Icons from '../icons/Icons';
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { MaterialUiPickersDate } from '@material-ui/pickers/typings/date';
+import { DatePickerType } from './Types';
+import { useTranslation } from 'react-i18next';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    display: 'flex',
+    alignItems: 'center',
+    marginLeft: '10px',
+    cursor: 'pointer',
+  },
+  icon: {
+    color: (props: any) =>
+      props.date ? theme.palette.primary.main : '#82838E',
+  },
+  label: {
+    marginLeft: '4px',
+    color: (props: any) =>
+      props.date ? theme.palette.primary.main : '#82838E',
+    fontWeight: 'bold',
+  },
+  picker: {
+    width: 0,
+  },
+  clear: {
+    fontSize: '14px',
+    color: theme.palette.primary.main,
+    marginLeft: '4px',
+  },
+}));
+
+export const CustomDatePicker: FC<DatePickerType> = props => {
+  const { onChange, label, date, setDate } = props;
+  const [open, setOpen] = useState(false);
+  const classes = useStyles({ date });
+  const { t: btnTrans } = useTranslation('btn');
+
+  const DatePickerIcon = Icons.datePicker;
+  const ClearIcon = Icons.clear;
+
+  const handleChange = (value: MaterialUiPickersDate) => {
+    setDate(value);
+    onChange(value);
+  };
+
+  const handleClear = (e: any) => {
+    e.stopPropagation();
+    handleChange(null);
+  };
+  return (
+    <>
+      <div
+        className={classes.wrapper}
+        onClick={() => {
+          setOpen(true);
+        }}
+      >
+        <DatePickerIcon className={classes.icon} />
+        <Typography className={classes.label}>{label}</Typography>
+        {date && <ClearIcon onClick={handleClear} className={classes.clear} />}
+      </div>
+
+      <DateTimePicker
+        className={classes.picker}
+        value={date}
+        open={open}
+        onChange={handleChange}
+        onClose={() => setOpen(false)}
+        okLabel={btnTrans('confirm')}
+      ></DateTimePicker>
+    </>
+  );
+};

+ 8 - 0
client/src/components/customDatePicker/Types.ts

@@ -0,0 +1,8 @@
+import { MaterialUiPickersDate } from '@material-ui/pickers/typings/date';
+
+export type DatePickerType = {
+  label: string;
+  onChange: (value: any) => void;
+  date: MaterialUiPickersDate;
+  setDate: (value: MaterialUiPickersDate) => void;
+};

+ 2 - 0
client/src/components/icons/Icons.tsx

@@ -22,6 +22,7 @@ import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
 import CachedIcon from '@material-ui/icons/Cached';
 import CachedIcon from '@material-ui/icons/Cached';
 import FilterListIcon from '@material-ui/icons/FilterList';
 import FilterListIcon from '@material-ui/icons/FilterList';
 import AlternateEmailIcon from '@material-ui/icons/AlternateEmail';
 import AlternateEmailIcon from '@material-ui/icons/AlternateEmail';
+import DatePicker from '@material-ui/icons/Event';
 import { SvgIcon } from '@material-ui/core';
 import { SvgIcon } from '@material-ui/core';
 import { ReactComponent as MilvusIcon } from '../../assets/icons/milvus.svg';
 import { ReactComponent as MilvusIcon } from '../../assets/icons/milvus.svg';
 import { ReactComponent as OverviewIcon } from '../../assets/icons/overview.svg';
 import { ReactComponent as OverviewIcon } from '../../assets/icons/overview.svg';
@@ -60,6 +61,7 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   refresh: (props = {}) => <CachedIcon {...props} />,
   refresh: (props = {}) => <CachedIcon {...props} />,
   filter: (props = {}) => <FilterListIcon {...props} />,
   filter: (props = {}) => <FilterListIcon {...props} />,
   alias: (props = {}) => <AlternateEmailIcon {...props} />,
   alias: (props = {}) => <AlternateEmailIcon {...props} />,
+  datePicker: (props = {}) => <DatePicker {...props} />,
 
 
   milvus: (props = {}) => (
   milvus: (props = {}) => (
     <SvgIcon viewBox="0 0 44 31" component={MilvusIcon} {...props} />
     <SvgIcon viewBox="0 0 44 31" component={MilvusIcon} {...props} />

+ 2 - 1
client/src/components/icons/Types.ts

@@ -33,4 +33,5 @@ export type IconsType =
   | 'refresh'
   | 'refresh'
   | 'filter'
   | 'filter'
   | 'copyExpression'
   | 'copyExpression'
-  | 'alias';
+  | 'alias'
+  | 'datePicker';

+ 33 - 0
client/src/hooks/TimeTravel.ts

@@ -0,0 +1,33 @@
+import dayjs from 'dayjs';
+import { formatUtcToMilvus } from '../utils/Format';
+import { useMemo, useState } from 'react';
+import { MaterialUiPickersDate } from '@material-ui/pickers/typings/date';
+import { useTranslation } from 'react-i18next';
+
+export const useTimeTravelHook = () => {
+  const [timeTravel, setTimeTravel] = useState<MaterialUiPickersDate>(null);
+  const { t: searchTrans } = useTranslation('search');
+
+  const timeTravelInfo = useMemo(() => {
+    const timestamp = dayjs(timeTravel).valueOf();
+    return {
+      label: timeTravel
+        ? ` ${searchTrans('timeTravelPrefix')} ${dayjs(timeTravel).format(
+            'YYYY-MM-DD HH:mm:ss'
+          )}`
+        : searchTrans('timeTravel'),
+      timestamp: timeTravel ? formatUtcToMilvus(timestamp) : undefined,
+    };
+  }, [searchTrans, timeTravel]);
+
+  const handleDateTimeChange = (value: MaterialUiPickersDate) => {
+    setTimeTravel(value);
+  };
+
+  return {
+    timeTravel,
+    setTimeTravel,
+    handleDateTimeChange,
+    timeTravelInfo,
+  };
+};

+ 2 - 0
client/src/i18n/cn/search.ts

@@ -13,6 +13,8 @@ const searchTrans = {
   filter: 'Advanced Filter',
   filter: 'Advanced Filter',
   vectorValueWarning:
   vectorValueWarning:
     'Vector value should be an array of length {{dimension}}(dimension)',
     'Vector value should be an array of length {{dimension}}(dimension)',
+  timeTravel: 'Time Travel',
+  timeTravelPrefix: 'Datas before',
 };
 };
 
 
 export default searchTrans;
 export default searchTrans;

+ 2 - 0
client/src/i18n/en/search.ts

@@ -12,6 +12,8 @@ const searchTrans = {
   topK: 'TopK {{number}}',
   topK: 'TopK {{number}}',
   filter: 'Advanced Filter',
   filter: 'Advanced Filter',
   vectorValueWarning: 'Vector value should be an array of length {{dimension}}',
   vectorValueWarning: 'Vector value should be an array of length {{dimension}}',
+  timeTravel: 'Time Travel',
+  timeTravelPrefix: 'Datas before',
 };
 };
 
 
 export default searchTrans;
 export default searchTrans;

+ 16 - 0
client/src/pages/query/Query.tsx

@@ -13,9 +13,12 @@ import Filter from '../../components/advancedSearch';
 import { CollectionHttp } from '../../http/Collection';
 import { CollectionHttp } from '../../http/Collection';
 import { FieldHttp } from '../../http/Field';
 import { FieldHttp } from '../../http/Field';
 import { usePaginationHook } from '../../hooks/Pagination';
 import { usePaginationHook } from '../../hooks/Pagination';
+// import { useTimeTravelHook } from '../../hooks/TimeTravel';
+
 import CopyButton from '../../components/advancedSearch/CopyButton';
 import CopyButton from '../../components/advancedSearch/CopyButton';
 import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
 import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
 import CustomToolBar from '../../components/grid/ToolBar';
 import CustomToolBar from '../../components/grid/ToolBar';
+// import { CustomDatePicker } from '../../components/customDatePicker/CustomDatePicker';
 
 
 const Query: FC<{
 const Query: FC<{
   collectionName: string;
   collectionName: string;
@@ -42,6 +45,9 @@ const Query: FC<{
 
 
   const classes = getQueryStyles();
   const classes = getQueryStyles();
 
 
+  // const { timeTravel, setTimeTravel, timeTravelInfo, handleDateTimeChange } =
+  //   useTimeTravelHook();
+
   // Format result list
   // Format result list
   const queryResultMemo = useMemo(
   const queryResultMemo = useMemo(
     () =>
     () =>
@@ -127,6 +133,7 @@ const Query: FC<{
       const res = await CollectionHttp.queryData(collectionName, {
       const res = await CollectionHttp.queryData(collectionName, {
         expr: expression,
         expr: expression,
         output_fields: fields.map(i => i.name),
         output_fields: fields.map(i => i.name),
+        // travel_timestamp: timeTravelInfo.timestamp,
       });
       });
       const result = res.data;
       const result = res.data;
       setQueryResult(result);
       setQueryResult(result);
@@ -185,6 +192,7 @@ const Query: FC<{
 
 
       <div className={classes.toolbar}>
       <div className={classes.toolbar}>
         <div className="left">
         <div className="left">
+          {/* <div className="expression"> */}
           <div>{`${expression || collectionTrans('exprPlaceHolder')}`}</div>
           <div>{`${expression || collectionTrans('exprPlaceHolder')}`}</div>
           <Filter
           <Filter
             ref={filterRef}
             ref={filterRef}
@@ -195,6 +203,14 @@ const Query: FC<{
             showTitle={false}
             showTitle={false}
             showTooltip={false}
             showTooltip={false}
           />
           />
+          {/* </div> */}
+
+          {/* <CustomDatePicker
+            label={timeTravelInfo.label}
+            onChange={handleDateTimeChange}
+            date={timeTravel}
+            setDate={setTimeTravel}
+          /> */}
         </div>
         </div>
         <div className="right">
         <div className="right">
           <CustomButton className="btn" onClick={handleFilterReset}>
           <CustomButton className="btn" onClick={handleFilterReset}>

+ 10 - 1
client/src/pages/query/Styles.ts

@@ -24,10 +24,19 @@ export const getQueryStyles = makeStyles((theme: Theme) => ({
       display: 'flex',
       display: 'flex',
       justifyContent: 'space-between',
       justifyContent: 'space-between',
       alignItems: 'center',
       alignItems: 'center',
-      width: 'calc(100% - 206px)',
+      flex: 1,
       padding: theme.spacing(0, 0, 0, 2),
       padding: theme.spacing(0, 0, 0, 2),
       fontSize: theme.spacing(2),
       fontSize: theme.spacing(2),
       backgroundColor: '#F9F9F9',
       backgroundColor: '#F9F9F9',
+
+      '& .expression': {
+        display: 'flex',
+        justifyContent: 'space-between',
+        flex: 1,
+        alignItems: 'center',
+        padding: theme.spacing(0, 1.5),
+        backgroundColor: '#F9F9F9',
+      },
     },
     },
 
 
     '& .right': {
     '& .right': {

+ 2 - 1
client/src/pages/query/Types.ts

@@ -2,4 +2,5 @@ export interface QueryParam {
   expr: string;
   expr: string;
   partitions_names?: string[];
   partitions_names?: string[];
   output_fields?: string[];
   output_fields?: string[];
-}
+  travel_timestamp?: string;
+}

+ 1 - 0
client/src/plugins/search/Types.ts

@@ -61,6 +61,7 @@ export interface VectorSearchParam {
   vectors: any;
   vectors: any;
   output_fields: string[];
   output_fields: string[];
   vector_type: number | DataTypeEnum;
   vector_type: number | DataTypeEnum;
+  travel_timestamp?: string;
 }
 }
 
 
 export interface SearchResult {
 export interface SearchResult {

+ 19 - 1
client/src/plugins/search/VectorSearch.tsx

@@ -16,7 +16,10 @@ import SimpleMenu from '../../components/menu/SimpleMenu';
 import { TOP_K_OPTIONS } from './Constants';
 import { TOP_K_OPTIONS } from './Constants';
 import { Option } from '../../components/customSelector/Types';
 import { Option } from '../../components/customSelector/Types';
 import { CollectionHttp } from '../../http/Collection';
 import { CollectionHttp } from '../../http/Collection';
-import { CollectionData, DataTypeEnum } from 'insight_src/pages/collections/Types';
+import {
+  CollectionData,
+  DataTypeEnum,
+} from 'insight_src/pages/collections/Types';
 import { IndexHttp } from '../../http/Index';
 import { IndexHttp } from '../../http/Index';
 import { getVectorSearchStyles } from './Styles';
 import { getVectorSearchStyles } from './Styles';
 import { parseValue } from '../../utils/Insert';
 import { parseValue } from '../../utils/Insert';
@@ -33,6 +36,8 @@ import Filter from '../../components/advancedSearch';
 import { Field } from '../../components/advancedSearch/Types';
 import { Field } from '../../components/advancedSearch/Types';
 import { useLocation } from 'react-router-dom';
 import { useLocation } from 'react-router-dom';
 import { parseLocationSearch } from '../../utils/Format';
 import { parseLocationSearch } from '../../utils/Format';
+import { CustomDatePicker } from 'insight_src/components/customDatePicker/CustomDatePicker';
+import { useTimeTravelHook } from 'insight_src/hooks/TimeTravel';
 
 
 const VectorSearch = () => {
 const VectorSearch = () => {
   useNavigationHook(ALL_ROUTER_TYPES.SEARCH);
   useNavigationHook(ALL_ROUTER_TYPES.SEARCH);
@@ -51,6 +56,7 @@ const VectorSearch = () => {
   // fields for advanced filter
   // fields for advanced filter
   const [filterFields, setFilterFields] = useState<Field[]>([]);
   const [filterFields, setFilterFields] = useState<Field[]>([]);
   const [selectedField, setSelectedField] = useState<string>('');
   const [selectedField, setSelectedField] = useState<string>('');
+
   // search params form
   // search params form
   const [searchParam, setSearchParam] = useState<{ [key in string]: number }>(
   const [searchParam, setSearchParam] = useState<{ [key in string]: number }>(
     {}
     {}
@@ -78,6 +84,9 @@ const VectorSearch = () => {
     handleGridSort,
     handleGridSort,
   } = usePaginationHook(searchResult || []);
   } = usePaginationHook(searchResult || []);
 
 
+  const { timeTravel, setTimeTravel, timeTravelInfo, handleDateTimeChange } =
+    useTimeTravelHook();
+
   const collectionOptions: Option[] = useMemo(
   const collectionOptions: Option[] = useMemo(
     () =>
     () =>
       collections.map(c => ({
       collections.map(c => ({
@@ -282,7 +291,9 @@ const VectorSearch = () => {
     setSearchResult(null);
     setSearchResult(null);
     setFilterFields([]);
     setFilterFields([]);
     setExpression('');
     setExpression('');
+    setTimeTravel(null);
   };
   };
+
   const handleSearch = async (topK: number, expr = expression) => {
   const handleSearch = async (topK: number, expr = expression) => {
     const searhParamPairs = {
     const searhParamPairs = {
       params: JSON.stringify(searchParam),
       params: JSON.stringify(searchParam),
@@ -298,6 +309,7 @@ const VectorSearch = () => {
       search_params: searhParamPairs,
       search_params: searhParamPairs,
       vectors: [parseValue(vectors)],
       vectors: [parseValue(vectors)],
       vector_type: fieldType,
       vector_type: fieldType,
+      travel_timestamp: timeTravelInfo.timestamp,
     };
     };
 
 
     setTableLoading(true);
     setTableLoading(true);
@@ -457,6 +469,12 @@ const VectorSearch = () => {
             filterDisabled={selectedField === '' || selectedCollection === ''}
             filterDisabled={selectedField === '' || selectedCollection === ''}
             onSubmit={handleAdvancedFilterChange}
             onSubmit={handleAdvancedFilterChange}
           />
           />
+          <CustomDatePicker
+            label={timeTravelInfo.label}
+            onChange={handleDateTimeChange}
+            date={timeTravel}
+            setDate={setTimeTravel}
+          />
         </div>
         </div>
         <div className="right">
         <div className="right">
           <CustomButton className="btn" onClick={handleReset}>
           <CustomButton className="btn" onClick={handleReset}>

+ 61 - 0
client/src/styles/theme.ts

@@ -2,7 +2,15 @@ import {
   // for strict mode
   // for strict mode
   unstable_createMuiStrictModeTheme as createMuiTheme,
   unstable_createMuiStrictModeTheme as createMuiTheme,
 } from '@material-ui/core/styles';
 } from '@material-ui/core/styles';
+import { MuiPickersOverrides } from '@material-ui/pickers/typings/overrides';
 
 
+type overridesNameToClassKey = {
+  [P in keyof MuiPickersOverrides]: keyof MuiPickersOverrides[P];
+};
+
+declare module '@material-ui/core/styles/overrides' {
+  export interface ComponentNameToClassKey extends overridesNameToClassKey {}
+}
 declare module '@material-ui/core/styles/createPalette' {
 declare module '@material-ui/core/styles/createPalette' {
   interface Palette {
   interface Palette {
     milvusBlue: Palette['primary'];
     milvusBlue: Palette['primary'];
@@ -172,5 +180,58 @@ export const theme = createMuiTheme({
         },
         },
       },
       },
     },
     },
+
+    // Date time picker theme overrides
+    MuiPickersToolbar: {
+      toolbar: {
+        '& .MuiTypography-h3': {
+          fontSize: '3rem',
+          lineHeight: 1.04,
+        },
+        '& .MuiTypography-h4': {
+          fontSize: '1.5rem',
+          lineHeight: 1.17,
+        },
+      },
+    },
+    MuiPickerDTTabs: {
+      tabs: {
+        backgroundColor: '#fff',
+        '& .MuiTabs-indicator': {
+          backgroundColor: commonThemes.palette.primary.main,
+        },
+      },
+    },
+    MuiPickersCalendarHeader: {
+      switchHeader: {
+        '& .MuiTypography-body1': {
+          fontSize: '0.85rem',
+        },
+      },
+      daysHeader: {
+        '& .MuiTypography-caption': {
+          fontSize: '0.85rem',
+        },
+      },
+    },
+
+    MuiPickersDay: {
+      day: {
+        '& .MuiTypography-body2': {
+          fontSize: '0.85rem',
+        },
+      },
+      daySelected: {
+        backgroundColor: commonThemes.palette.primary.main,
+        color: '#fff',
+      },
+      dayDisabled: {},
+      current: {},
+    },
+    MuiPickersModal: {
+      dialogAction: {
+        color: commonThemes.palette.primary.main,
+      },
+    },
   },
   },
 });
 });

+ 19 - 5
client/src/utils/Format.ts

@@ -144,7 +144,11 @@ export const getCreateFieldType = (config: Field): CreateFieldType => {
 export const formatAddress = (address: string): string => address.trim();
 export const formatAddress = (address: string): string => address.trim();
 
 
 // generate a sting like 20.22/98.33MB with proper unit
 // generate a sting like 20.22/98.33MB with proper unit
-export const getByteString = (value1: number, value2: number, capacityTrans: { [key in string]: string }) => {
+export const getByteString = (
+  value1: number,
+  value2: number,
+  capacityTrans: { [key in string]: string }
+) => {
   if (!value1 || !value2) return `0${capacityTrans.b}`;
   if (!value1 || !value2) return `0${capacityTrans.b}`;
   const power = Math.round(Math.log(value1) / Math.log(1024));
   const power = Math.round(Math.log(value1) / Math.log(1024));
   let unit = '';
   let unit = '';
@@ -168,8 +172,18 @@ export const getByteString = (value1: number, value2: number, capacityTrans: { [
       unit = capacityTrans.b;
       unit = capacityTrans.b;
       break;
       break;
   }
   }
-  const byteValue1 = value1 / (1024 ** power);
-  const byteValue2 = value2 / (1024 ** power);
+  const byteValue1 = value1 / 1024 ** power;
+  const byteValue2 = value2 / 1024 ** power;
 
 
-  return `${(byteValue1).toFixed(2)}/${(byteValue2).toFixed(2)} ${unit}`;
-}
+  return `${byteValue1.toFixed(2)}/${byteValue2.toFixed(2)} ${unit}`;
+};
+
+/**
+ * When number is larger than js max number, transform to string by BigInt.
+ * @param bigNumber
+ * @returns
+ */
+export const formatUtcToMilvus = (bigNumber: number) => {
+  const milvusTimeStamp = BigInt(bigNumber) << BigInt(18);
+  return milvusTimeStamp.toString();
+};

+ 47 - 2
client/yarn.lock

@@ -1172,6 +1172,13 @@
   dependencies:
   dependencies:
     regenerator-runtime "^0.13.4"
     regenerator-runtime "^0.13.4"
 
 
+"@babel/runtime@^7.6.0":
+  version "7.16.3"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
+  integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
 "@babel/template@^7.10.4", "@babel/template@^7.16.0", "@babel/template@^7.3.3":
 "@babel/template@^7.10.4", "@babel/template@^7.16.0", "@babel/template@^7.3.3":
   version "7.16.0"
   version "7.16.0"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6"
@@ -1227,6 +1234,18 @@
   resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
   resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
   integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
   integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
 
 
+"@date-io/core@1.x", "@date-io/core@^1.3.13":
+  version "1.3.13"
+  resolved "https://registry.yarnpkg.com/@date-io/core/-/core-1.3.13.tgz#90c71da493f20204b7a972929cc5c482d078b3fa"
+  integrity sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==
+
+"@date-io/dayjs@1.x":
+  version "1.3.13"
+  resolved "https://registry.yarnpkg.com/@date-io/dayjs/-/dayjs-1.3.13.tgz#3a9edf5a7227b31b0f00a4f640f8715626833a61"
+  integrity sha512-nD39xWYwQjDMIdpUzHIcADHxY9m1hm1DpOaRn3bc2rBdgmwQC0PfW0WYaHyGGP/6LEzEguINRbHuotMhf+T9Sg==
+  dependencies:
+    "@date-io/core" "^1.3.13"
+
 "@emotion/hash@^0.8.0":
 "@emotion/hash@^0.8.0":
   version "0.8.0"
   version "0.8.0"
   resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
   resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
@@ -1541,6 +1560,18 @@
     prop-types "^15.7.2"
     prop-types "^15.7.2"
     react-is "^16.8.0 || ^17.0.0"
     react-is "^16.8.0 || ^17.0.0"
 
 
+"@material-ui/pickers@^3.3.10":
+  version "3.3.10"
+  resolved "https://registry.yarnpkg.com/@material-ui/pickers/-/pickers-3.3.10.tgz#f1b0f963348cc191645ef0bdeff7a67c6aa25485"
+  integrity sha512-hS4pxwn1ZGXVkmgD4tpFpaumUaAg2ZzbTrxltfC5yPw4BJV+mGkfnQOB4VpWEYZw2jv65Z0wLwDE/piQiPPZ3w==
+  dependencies:
+    "@babel/runtime" "^7.6.0"
+    "@date-io/core" "1.x"
+    "@types/styled-jsx" "^2.2.8"
+    clsx "^1.0.2"
+    react-transition-group "^4.0.0"
+    rifm "^0.7.0"
+
 "@material-ui/styles@^4.11.4":
 "@material-ui/styles@^4.11.4":
   version "4.11.4"
   version "4.11.4"
   resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.4.tgz#eb9dfccfcc2d208243d986457dff025497afa00d"
   resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.4.tgz#eb9dfccfcc2d208243d986457dff025497afa00d"
@@ -2152,6 +2183,13 @@
   resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
   resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
   integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
   integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
 
 
+"@types/styled-jsx@^2.2.8":
+  version "2.2.9"
+  resolved "https://registry.yarnpkg.com/@types/styled-jsx/-/styled-jsx-2.2.9.tgz#e50b3f868c055bcbf9bc353eca6c10fdad32a53f"
+  integrity sha512-W/iTlIkGEyTBGTEvZCey8EgQlQ5l0DwMqi3iOXlLs2kyBwYTXHKEiU6IZ5EwoRwngL8/dGYuzezSup89ttVHLw==
+  dependencies:
+    "@types/react" "*"
+
 "@types/tapable@^1", "@types/tapable@^1.0.5":
 "@types/tapable@^1", "@types/tapable@^1.0.5":
   version "1.0.8"
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
   resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
@@ -3644,7 +3682,7 @@ cliui@^6.0.0:
     strip-ansi "^6.0.0"
     strip-ansi "^6.0.0"
     wrap-ansi "^6.2.0"
     wrap-ansi "^6.2.0"
 
 
-clsx@^1.0.4, clsx@^1.1.1:
+clsx@^1.0.2, clsx@^1.0.4, clsx@^1.1.1:
   version "1.1.1"
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
   resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
   integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
   integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
@@ -9901,7 +9939,7 @@ react-syntax-highlighter@^15.4.4:
     prismjs "^1.22.0"
     prismjs "^1.22.0"
     refractor "^3.2.0"
     refractor "^3.2.0"
 
 
-react-transition-group@^4.4.0:
+react-transition-group@^4.0.0, react-transition-group@^4.4.0:
   version "4.4.2"
   version "4.4.2"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
   integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==
   integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==
@@ -10252,6 +10290,13 @@ rgba-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
   resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
   integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
   integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
 
 
+rifm@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.7.0.tgz#debe951a9c83549ca6b33e5919f716044c2230be"
+  integrity sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+
 rimraf@^2.5.4, rimraf@^2.6.3:
 rimraf@^2.5.4, rimraf@^2.6.3:
   version "2.7.1"
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"