Skip to content

Commit

Permalink
Add key to MarkedMustache components and remove re-renders from confi…
Browse files Browse the repository at this point in the history
…g and auth context (#768)
  • Loading branch information
postrowinski committed Apr 3, 2023
1 parent f606759 commit 5b7a35f
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 70 deletions.
2 changes: 1 addition & 1 deletion mwdb/web/src/commons/auth/index.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { AuthContext } from "./context";
export { AuthProvider } from "./provider";
export { AuthProvider, localStorageAuthKey } from "./provider";
export { Capability, capabilitiesList } from "./capabilities";
39 changes: 25 additions & 14 deletions mwdb/web/src/commons/auth/provider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { useLocation, useNavigate } from "react-router-dom";

import { api } from "../api";

import { omit, isEqual, isNil } from "lodash";
import { Capability } from "./capabilities";
import { AuthContext } from "./context";

const localStorageAuthKey = "user";
export const localStorageAuthKey = "user";

function isSessionValid(authSession) {
if (!authSession || !authSession.token)
Expand Down Expand Up @@ -72,16 +73,20 @@ export function AuthProvider(props) {
const location = useLocation();
const navigate = useNavigate();
const [session, _setSession] = useState(getStoredAuthSession());
const refreshTimer = useRef(null);
const isAuthenticated = !!session;

function setSession(newSession) {
// Internal session setter which updates token used by Axios
// before populating new state to the components
_setSession(() => {
setTokenForAPI(newSession && newSession.token);
return newSession;
});
setTokenForAPI(getStoredAuthSession() && getStoredAuthSession().token);
if (isNil(newSession)) {
_setSession(null);
return;
}
const newSessionWithoutToken = { ...omit(newSession, "token") };
if (!isEqual(session, newSessionWithoutToken)) {
_setSession(newSessionWithoutToken);
}
}

function updateSession(newSession) {
Expand Down Expand Up @@ -140,7 +145,9 @@ export function AuthProvider(props) {
}

function hasCapability(capability) {
return isAuthenticated && session.capabilities.indexOf(capability) >= 0;
return (
isAuthenticated && session?.capabilities.indexOf(capability) >= 0
);
}

// Effect for 401 Not authenticated to handle unexpected session expiration
Expand Down Expand Up @@ -189,23 +196,27 @@ export function AuthProvider(props) {

// Effect for periodic session refresh
useEffect(() => {
let timer;
function setRefreshTimer() {
if (refreshTimer.current) return;
refreshTimer.current = setInterval(refreshSession, 60000);
refreshSession();
timer = setInterval(refreshSession, 60000);
}

function clearRefreshTimer() {
if (!refreshTimer.current) return;
clearInterval(refreshTimer.current);
refreshTimer.current = null;
clearInterval(timer);
}

// Set timer if user is logged in, clear otherwise
(isAuthenticated ? setRefreshTimer : clearRefreshTimer)();
return clearRefreshTimer;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isAuthenticated]);
}, [isAuthenticated, session]);

// Make sure that the token is not in the session.
useEffect(() => {
if (session?.token) {
_setSession(omit(session, "token"));
}
}, [session]);

// Synchronize session in another window with local session state
useEffect(() => {
Expand Down
8 changes: 6 additions & 2 deletions mwdb/web/src/commons/config/provider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React, {
useReducer,
useCallback,
} from "react";
import _ from "lodash";
import { api } from "../api";
import { ConfigContext } from "./context";
import { AuthContext } from "../auth";
Expand Down Expand Up @@ -69,7 +70,10 @@ export function ConfigProvider(props) {
async function updatePendingUsers() {
try {
const response = await api.getPendingUsers();
setPendingUsers(response.data["users"]);
const users = response.data["users"];
if (!_.isEqual(users, pendingUsers)) {
setPendingUsers(users);
}
} catch (error) {
setServerConfig({
type: configError,
Expand All @@ -78,7 +82,7 @@ export function ConfigProvider(props) {
}
}

const getPendingUsers = useCallback(updatePendingUsers, []);
const getPendingUsers = useCallback(updatePendingUsers, [pendingUsers]);

useEffect(() => {
updateServerInfo();
Expand Down
25 changes: 16 additions & 9 deletions mwdb/web/src/components/AttributesAddModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,22 @@ export default function AttributesAddModal({ isOpen, onAdd, onRequestClose }) {
}}
>
<tbody>
<RichAttributeRenderer
template={richTemplate}
value={
attributeType === "string"
? JSON.stringify(attributeValue)
: attributeValue
}
setInvalid={setInvalid}
/>
<tr>
<th>{"My attribute"}</th>
<td>
<RichAttributeRenderer
template={richTemplate}
value={
attributeType === "string"
? JSON.stringify(
attributeValue
)
: attributeValue
}
setInvalid={setInvalid}
/>
</td>
</tr>
</tbody>
</table>
</div>
Expand Down
24 changes: 15 additions & 9 deletions mwdb/web/src/components/Docs.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, { Suspense, useContext, useEffect, useState } from "react";
import React, { Suspense, useEffect, useState } from "react";

import { api } from "@mwdb-web/commons/api";
import { AuthContext } from "@mwdb-web/commons/auth";
import { localStorageAuthKey } from "@mwdb-web/commons/auth";
import { View } from "@mwdb-web/commons/ui";

const SwaggerUI = React.lazy(() => import("swagger-ui-react"));

export default function Docs() {
const auth = useContext(AuthContext);
const [apiSpec, setApiSpec] = useState({});

async function updateSpec() {
Expand All @@ -23,6 +22,18 @@ export default function Docs() {
setApiSpec(spec.data);
}

function requestInterceptor(req) {
const token = JSON.parse(
localStorage.getItem(localStorageAuthKey)
).token;

if (token) {
req.headers.Authorization = `Bearer ${token}`;
}

return req;
}

useEffect(() => {
updateSpec();
}, []);
Expand All @@ -34,12 +45,7 @@ export default function Docs() {
spec={apiSpec}
url=""
docExpansion="list"
onComplete={(swagger) => {
swagger.preauthorizeApiKey(
"bearerAuth",
auth.user.token
);
}}
requestInterceptor={requestInterceptor}
/>
</Suspense>
</View>
Expand Down
52 changes: 34 additions & 18 deletions mwdb/web/src/components/RichAttribute/MarkedMustache.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import Mustache from "mustache";
import { uniqueId } from "lodash";
import { lexer, defaults, Tokenizer } from "marked";
import { DataTable } from "@mwdb-web/commons/ui";
import { escapeSearchValue } from "@mwdb-web/commons/helpers";
Expand Down Expand Up @@ -214,27 +215,37 @@ function renderTokens(tokens, options) {
return token.text;
},
strong(token) {
return <strong>{renderTokens(token.tokens, options)}</strong>;
return (
<strong key={uniqueId()}>
{renderTokens(token.tokens, options)}
</strong>
);
},
em(token) {
return <em>{renderTokens(token.tokens, options)}</em>;
return (
<em key={uniqueId()}>{renderTokens(token.tokens, options)}</em>
);
},
del(token) {
return <del>{renderTokens(token.tokens, options)}</del>;
return (
<del key={uniqueId()}>
{renderTokens(token.tokens, options)}
</del>
);
},
hr(token) {
return <hr />;
return <hr key={uniqueId()} />;
},
blockquote(token) {
return (
<blockquote className="blockquote">
<blockquote key={uniqueId()} className="blockquote">
{renderTokens(token.tokens, options)}
</blockquote>
);
},
paragraph(token) {
return (
<p style={{ margin: "0" }}>
<p key={uniqueId()} style={{ margin: "0" }}>
{renderTokens(token.tokens, options)}
</p>
);
Expand All @@ -246,6 +257,7 @@ function renderTokens(tokens, options) {
"?" + new URLSearchParams({ q: query }).toString();
return (
<Link
key={uniqueId()}
to={{
pathname: options.searchEndpoint,
search,
Expand All @@ -256,37 +268,41 @@ function renderTokens(tokens, options) {
);
}
return (
<a href={token.href}>{renderTokens(token.tokens, options)}</a>
<a key={uniqueId()} href={token.href}>
{renderTokens(token.tokens, options)}
</a>
);
},
list(token) {
return (
<ul style={{ margin: "0" }}>
<ul key={uniqueId()} style={{ margin: "0" }}>
{token.items.map((item) => renderTokens([item]))}
</ul>
);
},
list_item(token) {
return <li>{renderTokens(token.tokens)}</li>;
return <li key={uniqueId()}>{renderTokens(token.tokens)}</li>;
},
html(token) {
return token.text;
},
table(token) {
return (
<DataTable>
<DataTable key={uniqueId()}>
<thead>
<tr>
{token.header.map((head) => (
<th>{renderTokens(head.tokens, options)}</th>
{token.header.map((head, index) => (
<th key={index}>
{renderTokens(head.tokens, options)}
</th>
))}
</tr>
</thead>
<tbody>
{token.rows.map((row) => (
<tr>
{row.map((cell) => (
<td>
{token.rows.map((row, rowsIndex) => (
<tr key={rowsIndex}>
{row.map((cell, cellIndex) => (
<td key={cellIndex}>
{renderTokens(cell.tokens, options)}
</td>
))}
Expand All @@ -297,10 +313,10 @@ function renderTokens(tokens, options) {
);
},
codespan(token) {
return <code>{token.text}</code>;
return <code key={uniqueId()}>{token.text}</code>;
},
code(token) {
return <pre>{token.text}</pre>;
return <pre key={uniqueId()}>{token.text}</pre>;
},
space() {
return [];
Expand Down
20 changes: 10 additions & 10 deletions mwdb/web/src/components/RichAttribute/RichAttributePreview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ export default function RichAttributePreview({
>
<option value="custom">(custom template)</option>
{exampleTemplates.map((value, index) => (
<option value={index}>{value.name}</option>
<option key={index} value={index}>
{value.name}
</option>
))}
</select>
<div className="row">
Expand Down Expand Up @@ -132,7 +134,7 @@ export default function RichAttributePreview({
type="radio"
id="showValue"
checked={!showContext}
onClick={() => setShowContext(false)}
onChange={() => setShowContext(false)}
/>
<label className="form-check-label" for="showValue">
Value
Expand All @@ -144,7 +146,7 @@ export default function RichAttributePreview({
type="radio"
id="showContext"
checked={showContext}
onClick={() => setShowContext(true)}
onChange={() => setShowContext(true)}
/>
<label className="form-check-label" for="showContext">
Context
Expand Down Expand Up @@ -174,13 +176,11 @@ export default function RichAttributePreview({
<div className="row">
<div className="col">
<strong>Preview</strong>
<DataTable>
<RichAttributeRenderer
template={template}
value={value}
setInvalid={setInvalid}
/>
</DataTable>
<RichAttributeRenderer
template={template}
value={value}
setInvalid={setInvalid}
/>
</div>
</div>
<div className="btn-group">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,5 @@ export default function RichAttributeRenderer({ template, value, setInvalid }) {
renderedValue = e.toString();
setInvalid(true);
}
return (
<tr>
<th>{"My attribute"}</th>
<td>{renderedValue}</td>
</tr>
);
return renderedValue;
}

0 comments on commit 5b7a35f

Please sign in to comment.