Checkpoint to get cypress working - auth-type from backend, less hard-coded auth0, improvements on query screen (less redundant fetches)

This commit is contained in:
2022-11-15 16:42:54 -06:00
parent c5780df58e
commit 94002f0d16
21 changed files with 2236939 additions and 344 deletions

View File

@ -23,6 +23,7 @@ import {useAuth0} from "@auth0/auth0-react";
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
import {QAppTreeNode} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppTreeNode";
import {QAuthenticationMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
import {QBrandingMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QBrandingMetaData";
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import CssBaseline from "@mui/material/CssBaseline";
@ -86,6 +87,7 @@ function getStaticRoutes()
];
}
const qController = QClient.getInstance();
export const SESSION_ID_COOKIE_NAME = "sessionId";
LicenseInfo.setLicenseKey(process.env.REACT_APP_MATERIAL_UI_LICENSE_KEY);
@ -105,24 +107,51 @@ export default function App()
return;
}
setLoadingToken(true);
(async () =>
{
try
const authenticationMetaData: QAuthenticationMetaData = await qController.getAuthenticationMetaData();
if (authenticationMetaData.type === "AUTH_0")
{
console.log("Loading token...");
await getAccessTokenSilently();
const idToken = await getIdTokenClaims();
setCookie(SESSION_ID_COOKIE_NAME, idToken.__raw, {path: "/"});
setIsFullyAuthenticated(true);
console.log("Token load complete.");
/////////////////////////////////////////
// use auth0 if auth type is ... auth0 //
/////////////////////////////////////////
try
{
console.log("Loading token...");
await getAccessTokenSilently();
const idToken = await getIdTokenClaims();
setCookie(SESSION_ID_COOKIE_NAME, idToken.__raw, {path: "/"});
setIsFullyAuthenticated(true);
console.log("Token load complete.");
}
catch (e)
{
console.log(`Error loading token: ${JSON.stringify(e)}`);
removeCookie(SESSION_ID_COOKIE_NAME);
qController.clearAuthenticationMetaDataLocalStorage();
logout();
return;
}
}
catch (e)
else if (authenticationMetaData.type === "FULLY_ANONYMOUS" || authenticationMetaData.type === "MOCK")
{
console.log(`Error loading token: ${JSON.stringify(e)}`);
removeCookie(SESSION_ID_COOKIE_NAME);
logout();
/////////////////////////////////////////////
// use a random token if anonymous or mock //
/////////////////////////////////////////////
console.log("Generating random token...");
setIsFullyAuthenticated(true);
setCookie(SESSION_ID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"});
console.log("Token generation complete.");
return;
}
else
{
console.log(`Unrecognized authenticationMetaData.type: ${authenticationMetaData.type}`);
qController.clearAuthenticationMetaDataLocalStorage();
}
})();
}, [loadingToken]);
@ -265,7 +294,7 @@ export default function App()
routeList.push({
name: `${app.label}`,
key: `${app.name}.dev`,
key: `${app.name}.record.dev`,
route: `${path}/:id/dev`,
component: <EntityDeveloperView table={table} />,
});
@ -342,13 +371,13 @@ export default function App()
let profileRoutes = {};
const gravatarBase = "https://www.gravatar.com/avatar/";
const hash = Md5.hashStr(user.email);
const hash = Md5.hashStr(user?.email || "user");
const profilePicture = `${gravatarBase}${hash}`;
profileRoutes = {
type: "collapse",
name: user.name,
key: user.name,
icon: <MDAvatar src={profilePicture} alt="{user.name}" size="sm" />,
name: user?.name,
key: "username",
icon: <MDAvatar src={profilePicture} alt="{user?.name}" size="sm" />,
collapse: [
{
name: "My Profile",
@ -388,11 +417,16 @@ export default function App()
}
catch (e)
{
console.error(e);
if (e instanceof QException)
{
if ((e as QException).message.indexOf("status code 401") !== -1)
{
removeCookie(SESSION_ID_COOKIE_NAME);
//////////////////////////////////////////////////////
// todo - this is auth0 logout... make more generic //
//////////////////////////////////////////////////////
logout();
return;
}

View File

@ -19,11 +19,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Auth0Provider, useAuth0} from "@auth0/auth0-react";
import {useAuth0} from "@auth0/auth0-react";
import React, {useEffect} from "react";
import {useCookies} from "react-cookie";
import {SESSION_ID_COOKIE_NAME} from "App";
import {AUTH0_CLIENT_ID, AUTH0_DOMAIN} from "index";
interface Props
{
@ -44,11 +43,7 @@ function HandleAuthorizationError({errorMessage}: Props)
});
return (
<Auth0Provider domain={AUTH0_DOMAIN} clientId={AUTH0_CLIENT_ID}>
<div>
<div>{errorMessage}</div>
</div>
</Auth0Provider>
<div>{errorMessage}</div>
);
}

View File

@ -20,6 +20,7 @@
*/
import {Auth0Provider} from "@auth0/auth0-react";
import {QAuthenticationMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
import React from "react";
import {render} from "react-dom";
import {BrowserRouter, useNavigate, useSearchParams} from "react-router-dom";
@ -28,52 +29,73 @@ import "qqq/styles/qqq-override-styles.css";
import {MaterialUIControllerProvider} from "context";
import HandleAuthorizationError from "HandleAuthorizationError";
import ProtectedRoute from "qqq/auth0/protected-route";
import QClient from "qqq/utils/QClient";
export const AUTH0_DOMAIN = process.env.REACT_APP_AUTH0_DOMAIN;
export const AUTH0_CLIENT_ID = process.env.REACT_APP_AUTH0_CLIENT_ID;
const qController = QClient.getInstance();
const authenticationMetaDataPromise: Promise<QAuthenticationMetaData> = qController.getAuthenticationMetaData()
// @ts-ignore
function Auth0ProviderWithRedirectCallback({children, ...props})
authenticationMetaDataPromise.then((authenticationMetaData) =>
{
const navigate = useNavigate();
const [searchParams] = useSearchParams();
// @ts-ignore
const onRedirectCallback = (appState) =>
function Auth0ProviderWithRedirectCallback({children, ...props})
{
navigate((appState && appState.returnTo) || window.location.pathname);
};
if (searchParams.get("error"))
const navigate = useNavigate();
const [searchParams] = useSearchParams();
// @ts-ignore
const onRedirectCallback = (appState) =>
{
navigate((appState && appState.returnTo) || window.location.pathname);
};
if (searchParams.get("error"))
{
return (
// @ts-ignore
<Auth0Provider {...props}>
<HandleAuthorizationError errorMessage={searchParams.get("error_description")} />
</Auth0Provider>
);
}
else
{
return (
// @ts-ignore
<Auth0Provider onRedirectCallback={onRedirectCallback} {...props}>
{children}
</Auth0Provider>
);
}
}
if (authenticationMetaData.type === "AUTH_0")
{
return (
// @ts-ignore
<Auth0Provider {...props}>
<HandleAuthorizationError errorMessage={searchParams.get("error_description")} />
</Auth0Provider>
const domain = process.env.REACT_APP_AUTH0_DOMAIN;
const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID;
render(
<BrowserRouter>
<Auth0ProviderWithRedirectCallback
domain={domain}
clientId={clientId}
redirectUri={`${window.location.origin}/dashboards/overview`}
>
<MaterialUIControllerProvider>
<ProtectedRoute component={App} />
</MaterialUIControllerProvider>
</Auth0ProviderWithRedirectCallback>
</BrowserRouter>,
document.getElementById("root"),
);
}
else
{
return (
// @ts-ignore
<Auth0Provider onRedirectCallback={onRedirectCallback} {...props}>
{children}
</Auth0Provider>
);
render(
<BrowserRouter>
<MaterialUIControllerProvider>
<App />
</MaterialUIControllerProvider>
</BrowserRouter>
, document.getElementById("root"));
}
}
render(
<BrowserRouter>
<Auth0ProviderWithRedirectCallback
domain={AUTH0_DOMAIN}
clientId={AUTH0_CLIENT_ID}
redirectUri={`${window.location.origin}/dashboards/overview`}
>
<MaterialUIControllerProvider>
<ProtectedRoute component={App} />
</MaterialUIControllerProvider>
</Auth0ProviderWithRedirectCallback>
</BrowserRouter>,
document.getElementById("root"),
);
})

View File

@ -19,12 +19,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType";
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
@ -44,10 +41,9 @@ import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Modal from "@mui/material/Modal";
import Tooltip from "@mui/material/Tooltip";
import {DataGridPro, getGridDateOperators, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridDensity, GridEventListener, GridExportMenuItemProps, GridFilterModel, GridLinkOperator, GridPinnedColumns, gridPreferencePanelStateSelector, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, MuiEvent, useGridApiContext, useGridApiEventHandler, useGridSelector} from "@mui/x-data-grid-pro";
import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator";
import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridDensity, GridEventListener, GridExportMenuItemProps, GridFilterModel, GridLinkOperator, GridPinnedColumns, gridPreferencePanelStateSelector, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, MuiEvent, useGridApiContext, useGridApiEventHandler, useGridSelector} from "@mui/x-data-grid-pro";
import React, {useContext, useEffect, useReducer, useRef, useState} from "react";
import {Link, useLocation, useNavigate, useSearchParams} from "react-router-dom";
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
import QContext from "QContext";
import DashboardLayout from "qqq/components/DashboardLayout";
import Footer from "qqq/components/Footer";
@ -55,7 +51,6 @@ import Navbar from "qqq/components/Navbar";
import {QActionsMenuButton, QCreateNewButton} from "qqq/components/QButtons";
import MDAlert from "qqq/components/Temporary/MDAlert";
import MDBox from "qqq/components/Temporary/MDBox";
import {buildQGridPvsOperators, QGridBooleanOperators, QGridNumericOperators, QGridStringOperators} from "qqq/pages/entity-list/QGridFilterOperators";
import ProcessRun from "qqq/pages/process-run";
import DataGridUtils from "qqq/utils/DataGridUtils";
import QClient from "qqq/utils/QClient";
@ -237,7 +232,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
const [recordIdsForProcess, setRecordIdsForProcess] = useState(null as string | QQueryFilter)
const [recordIdsForProcess, setRecordIdsForProcess] = useState(null as string | QQueryFilter);
const instance = useRef({timer: null});
@ -355,13 +350,26 @@ function EntityList({table, launchProcess}: Props): JSX.Element
return qFilter;
};
const getTableMetaData = async (): Promise<QTableMetaData> =>
{
if(tableMetaData !== null)
{
return(new Promise((resolve) =>
{
resolve(tableMetaData)
}));
}
return (qController.loadTableMetaData(tableName));
}
const updateTable = () =>
{
setLoading(true);
setRows([]);
(async () =>
{
const tableMetaData = await qController.loadTableMetaData(tableName);
const tableMetaData = await getTableMetaData();
setPageHeader(tableMetaData.label);
////////////////////////////////////////////////////////////////////////////////////////////////
@ -530,7 +538,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
const handlePinnedColumnsChange = (pinnedColumns: GridPinnedColumns) =>
{
setPinnedColumns(pinnedColumns)
setPinnedColumns(pinnedColumns);
localStorage.setItem(pinnedColumnsLocalStorageKey, JSON.stringify(pinnedColumns));
};
@ -1080,7 +1088,14 @@ function EntityList({table, launchProcess}: Props): JSX.Element
///////////////////////////////////////////////////////////////////////////////////////////
useEffect(() =>
{
updateTable();
if(latestQueryId > 0)
{
////////////////////////////////////////////////////////////////////////////////////////
// to avoid both this useEffect and the one below from both doing an "initial query", //
// only run this one if at least 1 query has already been ran //
////////////////////////////////////////////////////////////////////////////////////////
updateTable();
}
}, [ pageNumber, rowsPerPage, columnSortModel ]);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -32,6 +32,7 @@ class QClient
private static handleException(exception: QException)
{
// todo - check for 401 and clear cookie et al & logout?
console.log(`Caught Exception: ${JSON.stringify(exception)}`);
throw (exception);
}

View File

@ -32,30 +32,36 @@ class QProcessUtils
public static getProcessesForTable(metaData: QInstance, tableName: string, includeHidden = false): QProcessMetaData[]
{
const matchingProcesses: QProcessMetaData[] = [];
const processKeys = [...metaData.processes.keys()];
processKeys.forEach((key) =>
if (metaData.processes)
{
const process = metaData.processes.get(key);
if (process.tableName === tableName && (includeHidden || !process.isHidden))
const processKeys = [...metaData.processes.keys()];
processKeys.forEach((key) =>
{
matchingProcesses.push(process);
}
});
const process = metaData.processes.get(key);
if (process.tableName === tableName && (includeHidden || !process.isHidden))
{
matchingProcesses.push(process);
}
});
}
return matchingProcesses;
}
public static getReportsForTable(metaData: QInstance, tableName: string, includeHidden = false): QReportMetaData[]
{
const matchingReports: QReportMetaData[] = [];
const reportKeys = [...metaData.reports.keys()];
reportKeys.forEach((key) =>
if (metaData.reports)
{
const process = metaData.reports.get(key);
if (process.tableName === tableName)
const reportKeys = [...metaData.reports.keys()];
reportKeys.forEach((key) =>
{
matchingReports.push(process);
}
});
const process = metaData.reports.get(key);
if (process.tableName === tableName)
{
matchingReports.push(process);
}
});
}
return matchingReports;
}