Merge pull request #155 from AntlerVC/group-collections-table

Group collections table
This commit is contained in:
Shams
2020-08-06 17:38:30 +10:00
committed by GitHub
25 changed files with 169 additions and 79 deletions

13
www/app.yaml Normal file
View File

@@ -0,0 +1,13 @@
# [START runtime]
runtime: nodejs
env: flex
# [END runtime]
# [START handlers]
handlers:
- url: /
static_files: build/index.html
upload: build/index.html
- url: /(.*)$
static_files: build/\1
upload: build/(.*)

5
www/dispatch.yaml Normal file
View File

@@ -0,0 +1,5 @@
dispatch:
- url: "firetable-magic.uc.r.appspot.com/"
service: default
- url: "*.firetable.io/"
service: default

View File

@@ -63,9 +63,11 @@
"yup": "^0.27.0"
},
"scripts": {
"start": "react-scripts start",
"start": "serve -s build",
"prestart": "npm install -g serve",
"local": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"env": "node createDotEnv",
"target": "firebase target:apply hosting firetable",

View File

@@ -72,7 +72,12 @@ const App: React.FC = () => {
<PrivateRoute
exact
path={[routes.home, routes.tableWithId, routes.gridWithId]}
path={[
routes.home,
routes.tableWithId,
routes.tableGroupWithId,
routes.gridWithId,
]}
render={() => (
<FiretableContextProvider>
<Switch>
@@ -86,8 +91,8 @@ const App: React.FC = () => {
render={() => <TableView />}
/>
<PrivateRoute
path={routes.gridWithId}
render={() => <GridView />}
path={routes.tableGroupWithId}
render={() => <TableView />}
/>
</Switch>
</FiretableContextProvider>

View File

@@ -28,7 +28,6 @@ const searchClient = algoliasearch(
process.env.REACT_APP_ALGOLIA_APP_ID ?? "",
process.env.REACT_APP_ALGOLIA_SEARCH_API_KEY ?? ""
);
console.log("SEARCH CLIENT", searchClient);
export interface IPopupContentsProps
extends Omit<IConnectTableSelectProps, "className" | "TextFieldProps"> {}
@@ -60,8 +59,6 @@ export default function PopupContents({
const [search] = useDebouncedCallback(
async (query: string) => {
if (!algoliaIndex) return;
console.log("SEARCH", query, algoliaIndex, row);
const data = { ...userClaims, ...row };
const filters = config?.filters
? config?.filters.replace(/\{\{(.*?)\}\}/g, (m, k) => data[k])

View File

@@ -75,8 +75,9 @@ export default function Navigation({
// Get the table path, including filtering for user permissions
const getTablePath = (table: Table): LinkProps["to"] => {
if (!table || !userClaims) return "";
return table.collection;
return table.isCollectionGroup
? `/tableGroup/${table.collection}`
: `/table/${table.collection}`;
};
const currentCollection = tableCollection.split("/")[0];
@@ -160,6 +161,7 @@ export default function Navigation({
disabled={sectionName === section}
// onClick={handleSectionClick(sectionName)}
component={Link}
replace={true}
to={getTablePath(tables[0])}
color="inherit"
className={classes.sectionButton}

View File

@@ -54,8 +54,6 @@ function Action({
const snack = useContext(SnackContext);
const handleRun = () => {
setIsRunning(true);
console.log("RUN");
cloudFunction(
config.callableName,
{

View File

@@ -37,9 +37,9 @@ const useStyles = makeStyles(theme =>
},
})
);
export default function CodeEditor(props: any) {
const { handleChange, script } = props;
console.log({ script });
const classes = useStyles();
const editor = useRef<AceEditor>(null);
const handleResize = () => {

View File

@@ -28,7 +28,6 @@ const ColumnSelector = ({
};
useEffect(() => {
if (table) {
console.log({ table });
getColumns(table);
}
}, [table]);

View File

@@ -219,7 +219,6 @@ export default function FormDialog({
<ConfigForm
type={type}
handleChange={key => update => {
console.log(key, update);
setNewConfig({ ...newConfig, [key]: update });
}}
config={newConfig}

View File

@@ -21,6 +21,7 @@ import { SnackContext } from "contexts/snackContext";
import { useFiretableContext } from "contexts/firetableContext";
import { db } from "../../firebase";
import { FieldType } from "constants/fields";
import { isCollectionGroup } from "util/fns";
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
@@ -159,7 +160,9 @@ export default function ExportCSV() {
duration: 5000,
});
let query: any = db.collection(tableState?.tablePath!);
let query: any = isCollectionGroup()
? db.collectionGroup(tableState?.tablePath!)
: db.collection(tableState?.tablePath!);
// add filters
tableState?.filters.forEach(filter => {
query = query.where(filter.key, filter.operator, filter.value);

View File

@@ -13,7 +13,6 @@ const AlgoliaSelect = (props: any) => {
} = props;
const [searchState] = useAlgolia(algoliaIndex, algoliaKey, filters);
console.log(filters);
const [options, setOptions] = useState<any[]>([]);
useEffect(() => {
if (Array.isArray(searchState.results?.hits)) {

View File

@@ -9,8 +9,6 @@ const MigrateButton = ({ columns, needsMigration }) => {
const configDocPath = tableState?.config.tableConfig.path;
const handleColumnMigration = async () => {
console.log({ columns });
const newColumns = columns.reduce((acc, currCol, currIndex) => {
const baseCol = {
...currCol,

View File

@@ -9,6 +9,8 @@ import {
Typography,
Button,
} from "@material-ui/core";
import { isCollectionGroup } from "../../util/fns";
import AddIcon from "@material-ui/icons/Add";
import Filters from "./Filters";
@@ -84,25 +86,27 @@ export default function TableHeader({
className={classes.root}
>
<MigrateButton needsMigration={needsMigration} columns={tempColumns} />
<Grid item>
<Button
onClick={() => {
const initialVal = tempColumns.reduce((acc, currCol) => {
if (currCol.type === FieldType.checkbox) {
return { ...acc, [currCol.key]: false };
} else {
return acc;
}
}, {});
tableActions?.row.add(initialVal);
}}
variant="contained"
color="primary"
startIcon={<AddIcon />}
>
Add Row
</Button>
</Grid>
{!isCollectionGroup() && (
<Grid item>
<Button
onClick={() => {
const initialVal = tempColumns.reduce((acc, currCol) => {
if (currCol.type === FieldType.checkbox) {
return { ...acc, [currCol.key]: false };
} else {
return acc;
}
}, {});
tableActions?.row.add(initialVal);
}}
variant="contained"
color="primary"
startIcon={<AddIcon />}
>
Add Row
</Button>
</Grid>
)}
<Grid item>{/* <HiddenFields /> */}</Grid>
<Grid item>
@@ -150,9 +154,11 @@ export default function TableHeader({
<Grid item />
<Grid item>
<ImportCSV />
</Grid>
{!isCollectionGroup() && (
<Grid item>
<ImportCSV />
</Grid>
)}
<Grid item>
<ExportCSV />

View File

@@ -44,12 +44,7 @@ export type FiretableColumn = Column<any> & {
[key: string]: any;
};
interface ITableProps {
collection: string;
filters: FireTableFilter[];
}
export default function Table({ collection, filters }: ITableProps) {
export default function Table() {
const classes = useStyles();
const theme = useTheme();
const finalColumnClasses = useFinalColumnStyles();
@@ -64,12 +59,6 @@ export default function Table({ collection, filters }: ITableProps) {
const { userDoc } = useAppContext();
const userDocHiddenFields =
userDoc.state.doc?.tables[`${tableState?.tablePath}`]?.hiddenFields ?? [];
useEffect(() => {
if (tableActions && tableState && tableState.tablePath !== collection) {
tableActions.table.set(collection, filters);
if (sideDrawerRef?.current) sideDrawerRef.current.setCell!(null);
}
}, [collection]);
const rowsContainerRef = useRef<HTMLDivElement>(null);
// Gets more rows when scrolled down.
@@ -138,7 +127,7 @@ export default function Table({ collection, filters }: ITableProps) {
const rows = tableState.rows;
const rowGetter = (rowIdx: number) => rows[rowIdx];
const inSubTable = collection.split("/").length > 1;
const inSubTable = tableState.tablePath.split("/").length > 1;
let tableWidth: any = `calc(100% - ${
DRAWER_COLLAPSED_WIDTH
@@ -152,7 +141,7 @@ export default function Table({ collection, filters }: ITableProps) {
<Hotkeys selectedCell={selectedCell} />
</Suspense> */}
{inSubTable && <SubTableBreadcrumbs collection={collection} />}
{inSubTable && <SubTableBreadcrumbs collection={tableState.tablePath} />}
<TableHeader
rowHeight={rowHeight}

View File

@@ -11,11 +11,12 @@ import {
DialogTitle,
TextField,
Button,
Select,
Typography,
Switch,
FormControlLabel,
} from "@material-ui/core";
import { useFiretableContext } from "../contexts/firetableContext";
import OptionsInput from "./Table/ColumnMenu/ConfigFields/OptionsInput";
export enum TableSettingsDialogModes {
create,
update,
@@ -31,6 +32,7 @@ export interface ICreateTableDialogProps {
section: string;
description: string;
name: string;
isCollectionGroup: boolean;
} | null;
}
@@ -40,6 +42,7 @@ const FORM_EMPTY_STATE = {
collection: "",
description: "",
roles: ["ADMIN"],
isCollectionGroup: false,
};
export default function TableSettingsDialog({
mode,
@@ -66,7 +69,11 @@ export default function TableSettingsDialog({
settingsActions?.createTable(formState);
if (router.location.pathname === "/") {
router.history.push(`table/${formState.collection}`);
router.history.push(
`${formState.isCollectionGroup ? "tableGroup" : "table"}/${
formState.collection
}`
);
} else {
router.history.push(formState.collection);
}
@@ -123,6 +130,16 @@ export default function TableSettingsDialog({
/>
)}
<FormControlLabel
control={<Switch />}
label={"isCollectionGroup"}
labelPlacement="start"
value={formState.isCollectionGroup}
onChange={e =>
handleChange("isCollectionGroup", !formState.isCollectionGroup)
}
// classes={{ root: classes.formControlLabel, label: classes.label }}
/>
<TextField
value={formState.section}
onChange={e => handleChange("section", e.target.value)}

View File

@@ -5,7 +5,10 @@ export enum routes {
signOut = "/signOut",
table = "/table",
tableGroup = "/tableGroup",
tableWithId = "/table/:id",
tableGroupWithId = "/tableGroup/:id",
grid = "/grid",
gridWithId = "/grid/:id",
editor = "/editor",

View File

@@ -19,6 +19,7 @@ export type Table = {
roles: string[];
description: string;
section: string;
isCollectionGroup: boolean;
};
interface FiretableContextProps {

View File

@@ -0,0 +1,43 @@
import firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";
import "firebase/functions";
import "firebase/storage";
export let auth: any = false;
export let db: any = false;
export let bucket: any = false;
export let functions: any = false;
export let googleProvider: any = false;
console.log(`fetching config for ${window.location.hostname.split(".")[0]}`);
fetch(
`https://us-central1-firetable-magic.cloudfunctions.net/getWebAppConfig?projectId=${window.location.hostname.split(
"."
)[0] ?? "antler-vc"}`
)
.then(async response => {
const config = await response.json();
console.log({ config });
firebase.initializeApp(config);
auth = firebase.auth();
db = firebase.firestore();
// db.settings({ cacheSizeBytes: firebase.firestore.CACHE_SIZE_UNLIMITED });
// db.enablePersistence({ synchronizeTabs: true });
bucket = firebase.storage();
functions = firebase.functions();
googleProvider = new firebase.auth.GoogleAuthProvider().setCustomParameters(
{
prompt: "select_account",
}
);
})
.catch(err => console.log(err));
export const deleteField = firebase.firestore.FieldValue.delete;

View File

@@ -49,11 +49,6 @@ const useFiretable = (
orderBy,
});
/**Subscribes to _FIRETABLE_/settings/schema to have table configuration pre-cached */
const [settingsState, setttingsDispatch] = useTable({
path: "_FIRETABLE_/settings/schema",
});
/** set collection path of table */
const setTable = (collectionName: string, filters: FireTableFilter[]) => {
if (collectionName !== tableState.path || filters !== tableState.filters) {

View File

@@ -7,6 +7,7 @@ import firebase from "firebase/app";
import { FireTableFilter, FiretableOrderBy } from ".";
import { SnackContext } from "../../contexts/snackContext";
import { cloudFunction } from "../../firebase/callables";
import { isCollectionGroup } from "util/fns";
const CAP = 1000; // safety paramter sets the upper limit of number of docs fetched by this hook
const serverTimestamp = firebase.firestore.FieldValue.serverTimestamp;
var characters =
@@ -87,7 +88,9 @@ const useTable = (initialOverrides: any) => {
});
let query:
| firebase.firestore.CollectionReference
| firebase.firestore.Query = db.collection(tableState.path);
| firebase.firestore.Query = isCollectionGroup()
? db.collectionGroup(tableState.path)
: db.collection(tableState.path);
filters.forEach(filter => {
query = query.where(filter.key, filter.operator, filter.value);

View File

@@ -6,7 +6,7 @@ import _camelCase from "lodash/camelCase";
import _findIndex from "lodash/findIndex";
import _find from "lodash/find";
import _sortBy from "lodash/sortBy";
import { arrayMover } from "../../util/fns";
import { arrayMover, isCollectionGroup } from "../../util/fns";
import { db, deleteField } from "../../firebase";
//import
@@ -14,10 +14,9 @@ import { db, deleteField } from "../../firebase";
const formatPathRegex = /\/[^\/]+\/([^\/]+)/g;
const formatPath = (tablePath: string) => {
return `_FIRETABLE_/settings/schema/${tablePath.replace(
formatPathRegex,
"/subTables/$1"
)}`;
return `_FIRETABLE_/settings/${
isCollectionGroup() ? "groupSchema" : "schema"
}/${tablePath.replace(formatPathRegex, "/subTables/$1")}`;
};
const useTableConfig = (tablePath?: string) => {
const [tableConfigState, documentDispatch] = useDoc({
@@ -50,7 +49,6 @@ const useTableConfig = (tablePath?: string) => {
const add = (name: string, type: FieldType, data?: any) => {
//TODO: validation
const { columns } = tableConfigState;
console.log({ columns });
const newIndex = Object.keys(columns).length;
let updatedColumns = columns;
const key = _camelCase(name);

View File

@@ -53,3 +53,8 @@ export const sanitiseRowData = (rowData: any) => {
});
return rowData;
};
export const isCollectionGroup = () => {
const pathName = window.location.pathname.split("/")[1];
return pathName === "tableGroup";
};

View File

@@ -1,5 +1,6 @@
import React from "react";
import React, { useEffect } from "react";
import queryString from "query-string";
import { useFiretableContext } from "contexts/firetableContext";
import { Hidden } from "@material-ui/core";
@@ -13,7 +14,7 @@ import useRouter from "hooks/useRouter";
export default function TableView() {
const router = useRouter();
const tableCollection = decodeURIComponent(router.match.params.id);
const { tableState, tableActions, sideDrawerRef } = useFiretableContext();
let filters: FireTableFilter[] = [];
const parsed = queryString.parse(router.location.search);
if (typeof parsed.filters === "string") {
@@ -23,14 +24,20 @@ export default function TableView() {
//TODO: json schema validator
}
useEffect(() => {
if (
tableActions &&
tableState &&
tableState.tablePath !== tableCollection
) {
tableActions.table.set(tableCollection, filters);
if (sideDrawerRef?.current) sideDrawerRef.current.setCell!(null);
}
}, [tableCollection]);
if (!tableState?.tablePath) return <></>;
return (
<Navigation tableCollection={tableCollection}>
<Table
key={tableCollection}
collection={tableCollection}
filters={filters}
/>
<Table key={tableCollection} />
<Hidden smDown>
<SideDrawer />
</Hidden>

View File

@@ -89,6 +89,7 @@ const TablesView = () => {
roles: string[];
name: string;
section: string;
isCollectionGroup: boolean;
};
}>({
mode: null,
@@ -137,7 +138,9 @@ const TablesView = () => {
}
bodyContent={table.description}
primaryLink={{
to: `${routes.table}/${table.collection}`,
to: `${
table.isCollectionGroup ? routes.tableGroup : routes.table
}/${table.collection}`,
label: "Open",
}}
secondaryAction={