diff --git a/ft_build/sparksLib/bigqueryIndex.ts b/ft_build/sparksLib/bigqueryIndex.ts index 9341a5898..c5539ab81 100644 --- a/ft_build/sparksLib/bigqueryIndex.ts +++ b/ft_build/sparksLib/bigqueryIndex.ts @@ -163,7 +163,7 @@ const transformToSQLType = (ftType: string) => { }; const bigqueryIndex = async (payload, sparkContext) => { - const { objectID, index, fieldsToSync, projectID } = payload; + const { objectID, index, fieldsToSync, projectID, datasetLocation } = payload; const { triggerType, change, fieldTypes } = sparkContext; const record = rowReducer(fieldsToSync, sparkContext.row); @@ -178,7 +178,9 @@ const bigqueryIndex = async (payload, sparkContext) => { // create dataset with exact name "firetable" if not exists async function preprocessDataset() { - const dataset = bigquery.dataset("firetable"); + const dataset = bigquery.dataset("firetable", { + location: datasetLocation ?? "US", + }); const res = await dataset.exists(); const exists = res[0]; if (!exists) { diff --git a/www/package.json b/www/package.json index 343ed3b64..237435c4e 100644 --- a/www/package.json +++ b/www/package.json @@ -4,7 +4,7 @@ "homepage": "https://firetable.io/", "repository": { "type": "git", - "url": "https://github.com/AntlerVC/firetable.git" + "url": "https://github.com/FiretableProject/firetable.git" }, "private": true, "dependencies": { diff --git a/www/src/components/Auth/FirebaseUi.tsx b/www/src/components/Auth/FirebaseUi.tsx index 90c67a818..a18ec397a 100644 --- a/www/src/components/Auth/FirebaseUi.tsx +++ b/www/src/components/Auth/FirebaseUi.tsx @@ -188,7 +188,14 @@ export default function FirebaseUi(props: Partial) { useEffect(() => { db.doc("/_FIRETABLE_/publicSettings") .get() - .then((doc) => setSignInOptions(doc?.get("signInOptions"))) + .then((doc) => { + const options = doc?.get("signInOptions"); + if (!options) { + setSignInOptions(["google"]); + } else { + setSignInOptions(options); + } + }) .catch(() => setSignInOptions(["google"])); }, []); diff --git a/www/src/components/Navigation/UpdateChecker.tsx b/www/src/components/Navigation/UpdateChecker.tsx new file mode 100644 index 000000000..aa3b66b64 --- /dev/null +++ b/www/src/components/Navigation/UpdateChecker.tsx @@ -0,0 +1,154 @@ +import { useState, useEffect } from "react"; +import createPersistedState from "use-persisted-state"; +import { differenceInDays } from "date-fns"; + +import { + makeStyles, + createStyles,}from'@material-ui/styles' +import { + MenuItem, + ListItemText, + ListItemSecondaryAction, + Link, +} from "@material-ui/core"; +import OpenInNewIcon from "@material-ui/icons/OpenInNew"; + +import meta from "../../../package.json"; +import WIKI_LINKS from "constants/wikiLinks"; + +const useLastCheckedUpdateState = createPersistedState( + "_FT_LAST_CHECKED_UPDATE" +); +export const useLatestUpdateState = createPersistedState("_FT_LATEST_UPDATE"); + +const useStyles = makeStyles((theme) => + createStyles({ + secondaryAction: { pointerEvents: "none" }, + secondaryIcon: { + display: "block", + color: theme.palette.action.active, + }, + + version: { + display: "block", + padding: theme.spacing(1, 2), + userSelect: "none", + color: theme.palette.text.disabled, + }, + }) +); + +export default function UpdateChecker() { + const classes = useStyles(); + + const [ + lastCheckedUpdate, + setLastCheckedUpdate, + ] = useLastCheckedUpdateState(); + const [latestUpdate, setLatestUpdate] = useLatestUpdateState>(null); + + const [checkState, setCheckState] = useState( + null + ); + + const checkForUpdate = async () => { + setCheckState("LOADING"); + + // https://docs.github.com/en/rest/reference/repos#get-the-latest-release + const endpoint = meta.repository.url + .replace("github.com", "api.github.com/repos") + .replace(/.git$/, "/releases/latest"); + try { + const res = await fetch(endpoint, { + headers: { + Accept: "application/vnd.github.v3+json", + }, + }); + const json = await res.json(); + + if (json.tag_name > "v" + meta.version) { + setLatestUpdate(json); + setCheckState(null); + } else { + setCheckState("NO_UPDATE"); + } + + setLastCheckedUpdate(new Date().toISOString()); + } catch (e) { + console.error(e); + setLatestUpdate(null); + setCheckState("NO_UPDATE"); + } + }; + + // Check for new updates on page load, if last check was more than 7 days ago + useEffect(() => { + if (!lastCheckedUpdate) checkForUpdate(); + else if (differenceInDays(new Date(), new Date(lastCheckedUpdate)) > 7) + checkForUpdate(); + }, [lastCheckedUpdate]); + + // Verify latest update is not installed yet + useEffect(() => { + if (latestUpdate?.tag_name <= "v" + meta.version) setLatestUpdate(null); + }, [latestUpdate, setLatestUpdate]); + + return ( + <> + {checkState === "LOADING" ? ( + Checking for updates… + ) : checkState === "NO_UPDATE" ? ( + No updates available + ) : latestUpdate === null ? ( + Check for updates + ) : ( + <> + + + + + + + + + + + + + + + )} + + + {meta.name} v{meta.version} + + + ); +} diff --git a/www/src/components/Navigation/UserMenu.tsx b/www/src/components/Navigation/UserMenu.tsx index e15235d1d..5181531ac 100644 --- a/www/src/components/Navigation/UserMenu.tsx +++ b/www/src/components/Navigation/UserMenu.tsx @@ -7,22 +7,24 @@ import { IconButtonProps, Avatar, Menu, - Link as MuiLink, MenuItem, ListItemAvatar, ListItemText, ListItemSecondaryAction, ListItemIcon, Divider, + Badge, } from "@material-ui/core"; import AccountCircleIcon from "@material-ui/icons/AccountCircle"; import ArrowRightIcon from "@material-ui/icons/ArrowRight"; import CheckIcon from "@material-ui/icons/Check"; +import UpdateChecker, { useLatestUpdateState } from "./UpdateChecker"; import { useAppContext } from "contexts/AppContext"; import routes from "constants/routes"; -import meta from "../../../package.json"; import { projectId } from "../../firebase"; +import meta from "../../../package.json"; + const useStyles = makeStyles((theme) => createStyles({ spacer: { @@ -54,13 +56,6 @@ const useStyles = makeStyles((theme) => theme.palette.background.paper, marginTop: theme.spacing(-1), }, - - version: { - display: "block", - padding: theme.spacing(1, 2), - userSelect: "none", - color: theme.palette.text.disabled, - }, }) ); @@ -70,6 +65,7 @@ export default function UserMenu(props: IconButtonProps) { const anchorEl = useRef(null); const [open, setOpen] = useState(false); const [themeSubMenu, setThemeSubMenu] = useState(null); + const [latestUpdate] = useLatestUpdateState>(); const { currentUser, @@ -124,7 +120,13 @@ export default function UserMenu(props: IconButtonProps) { onClick={() => setOpen(true)} className={classes.iconButton} > - {avatar} + {latestUpdate?.tag_name > "v" + meta.version ? ( + + {avatar} + + ) : ( + avatar + )} - - {meta.name} v{meta.version} - + ); diff --git a/www/src/components/Navigation/index.tsx b/www/src/components/Navigation/index.tsx index ea0c59395..5512a1f4e 100644 --- a/www/src/components/Navigation/index.tsx +++ b/www/src/components/Navigation/index.tsx @@ -88,7 +88,7 @@ export default function Navigation({ - {/* */} + {/* */} diff --git a/www/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx b/www/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx index cda92f106..d0982f6f8 100644 --- a/www/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx +++ b/www/src/components/Table/ColumnMenu/FieldSettings/DefaultValueInput.tsx @@ -16,6 +16,7 @@ import CodeEditorHelper from "components/CodeEditorHelper"; import CodeEditor from "components/Table/editors/CodeEditor"; import FormAutosave from "./FormAutosave"; import { FieldType } from "constants/fields"; +import WIKI_LINKS from "constants/wikiLinks"; const useStyles = makeStyles((theme) => createStyles({ @@ -129,7 +130,7 @@ export default function DefaultValueInput({ {config.defaultValue?.type === "dynamic" && ( <> - +
- Cloud Run trigger URL not configured. Configuration guide:{" "} + Cloud Run trigger URL not configured. - https://github.com/FiretableProject/firetable/wiki/Setting-up-cloud-Run-FT-Builder + Configuration guide } diff --git a/www/src/components/Table/editors/TextEditor.tsx b/www/src/components/Table/editors/TextEditor.tsx index bc677315d..456ebcf2e 100644 --- a/www/src/components/Table/editors/TextEditor.tsx +++ b/www/src/components/Table/editors/TextEditor.tsx @@ -1,4 +1,4 @@ -import { useRef, useEffect } from "react"; +import { useRef, useLayoutEffect } from "react"; import { EditorProps } from "react-data-grid"; import { makeStyles, createStyles } from "@material-ui/styles"; @@ -48,7 +48,7 @@ export default function TextEditor({ row, column }: EditorProps) { const inputRef = useRef(null); - useEffect(() => { + useLayoutEffect(() => { return () => { const newValue = inputRef.current?.value; if (newValue !== undefined && updateCell) { diff --git a/www/src/components/TableSettings/form.tsx b/www/src/components/TableSettings/form.tsx index b22e6330b..670a2e7cc 100644 --- a/www/src/components/TableSettings/form.tsx +++ b/www/src/components/TableSettings/form.tsx @@ -5,6 +5,7 @@ import { Link, Typography } from "@material-ui/core"; import OpenInNewIcon from "@material-ui/icons/OpenInNew"; import { MONO_FONT } from "Themes"; import { projectId } from "../../firebase"; +import WIKI_LINKS from "constants/wikiLinks"; export const tableSettings = ( mode: TableSettingsDialogModes | null, @@ -117,7 +118,7 @@ export const tableSettings = ( Choose which roles have access to this table. Remember to set the appropriate Firestore Security Rules for this collection. import( @@ -45,7 +47,7 @@ const Settings = ({ config, handleChange }) => { }} /> derivative script - + }> WIKI_LINK_ROOT + path +); +export default WIKI_LINKS; diff --git a/www/src/pages/Auth/SetupGuide.tsx b/www/src/pages/Auth/SetupGuide.tsx index b033d5d44..79a2af681 100644 --- a/www/src/pages/Auth/SetupGuide.tsx +++ b/www/src/pages/Auth/SetupGuide.tsx @@ -2,6 +2,7 @@ import { Typography, Button } from "@material-ui/core"; import OpenInNewIcon from "@material-ui/icons/OpenInNew"; import AuthLayout from "components/Auth/AuthLayout"; +import WIKI_LINKS from "constants/wikiLinks"; export default function AuthSetupGuide() { return ( @@ -20,7 +21,7 @@ export default function AuthSetupGuide() { variant="contained" endIcon={} component="a" - href="https://github.com/FiretableProject/firetable/wiki/Set-Up-Firebase-Authentication" + href={WIKI_LINKS.setUpAuth} target="_blank" rel="noopener" > diff --git a/www/src/pages/Home.tsx b/www/src/pages/Home.tsx index a4e183907..c43e88158 100644 --- a/www/src/pages/Home.tsx +++ b/www/src/pages/Home.tsx @@ -32,6 +32,7 @@ import TableSettingsDialog, { import ProjectSettings from "components/ProjectSettings"; import EmptyState from "components/EmptyState"; +import WIKI_LINKS from "constants/wikiLinks"; const useStyles = makeStyles((theme) => createStyles({ "@global": { @@ -140,11 +141,7 @@ export default function HomePage() { If you are the project owner please follow the instructions{" "} - + here {" "} to setup the project rules.