chore: Improve relationship loading, include policies (#6321)

Use model where available in usePolicy
This commit is contained in:
Tom Moor
2023-12-28 12:51:33 -04:00
committed by GitHub
parent bd7d5c338d
commit 79764b1e64
16 changed files with 37 additions and 24 deletions

View File

@@ -11,7 +11,6 @@ import useStores from "./useStores";
*/
export default function usePolicy(entity?: string | Model | null) {
const { policies } = useStores();
const triggered = React.useRef(false);
const entityId = entity
? typeof entity === "string"
? entity
@@ -20,12 +19,9 @@ export default function usePolicy(entity?: string | Model | null) {
React.useEffect(() => {
if (entity && typeof entity !== "string") {
// The policy for this model is missing and we haven't tried to fetch it
// yet, go ahead and do that now. The force flag is needed otherwise the
// network request will be skipped due to the model existing in the store
if (!policies.get(entity.id) && !triggered.current) {
triggered.current = true;
void entity.store.fetch(entity.id, { force: true });
// The policy for this model is missing, reload relationships for this model.
if (!policies.get(entity.id)) {
void entity.loadRelations();
}
}
}, [policies, entity]);

View File

@@ -32,7 +32,7 @@ function CommentMenu({ comment, onEdit, onDelete, className }: Props) {
});
const { documents, dialogs } = useStores();
const { t } = useTranslation();
const can = usePolicy(comment.id);
const can = usePolicy(comment);
const document = documents.get(comment.documentId);
const handleDelete = React.useCallback(() => {

View File

@@ -16,7 +16,7 @@ type Props = {
function FileOperationMenu({ fileOperation, onDelete }: Props) {
const { t } = useTranslation();
const can = usePolicy(fileOperation.id);
const can = usePolicy(fileOperation);
const menu = useMenuState({
modal: true,
});

View File

@@ -24,7 +24,7 @@ function ShareMenu({ share }: Props) {
const { shares } = useStores();
const { t } = useTranslation();
const history = useHistory();
const can = usePolicy(share.id);
const can = usePolicy(share);
const handleGoToDocument = React.useCallback(
(ev: React.SyntheticEvent) => {

View File

@@ -30,7 +30,7 @@ function UserMenu({ user }: Props) {
const menu = useMenuState({
modal: true,
});
const can = usePolicy(user.id);
const can = usePolicy(user);
const context = useActionContext({
isContextMenu: true,
});

View File

@@ -32,26 +32,39 @@ export default abstract class Model {
}
/**
* Ensures all the defined relations for the model are in memory
* Ensures all the defined relations and policies for the model are in memory.
*
* @returns A promise that resolves when loading is complete.
*/
async loadRelations() {
async loadRelations(): Promise<any> {
const relations = getRelationsForModelClass(
this.constructor as typeof Model
);
if (!relations) {
return;
}
if (this.loadingRelations) {
return this.loadingRelations;
}
const promises = [];
for (const properties of relations.values()) {
const store = this.store.rootStore.getStoreForModelName(
properties.relationClassResolver().modelName
);
if ("fetch" in store) {
await store.fetch(this[properties.idKey]);
promises.push(store.fetch(this[properties.idKey]));
}
}
const policy = this.store.rootStore.policies.get(this.id);
if (!policy) {
promises.push(this.store.fetch(this.id, { force: true }));
}
this.loadingRelations = Promise.all(promises);
return await this.loadingRelations;
}
/**
@@ -175,4 +188,9 @@ export default abstract class Model {
}
protected persistedAttributes: Partial<Model> = {};
/**
* A promise that resolves when all relations have been loaded
*/
private loadingRelations: Promise<any[]> | undefined;
}

View File

@@ -55,7 +55,7 @@ function CollectionScene() {
const id = params.id || "";
const collection: Collection | null | undefined =
collections.getByUrl(id) || collections.get(id);
const can = usePolicy(collection?.id || "");
const can = usePolicy(collection);
React.useEffect(() => {
setLastVisitedPath(currentPath);

View File

@@ -102,7 +102,7 @@ function SharedDocumentScene(props: Props) {
)
? (searchParams.get("theme") as Theme)
: undefined;
const can = usePolicy(response?.document.id ?? "");
const can = usePolicy(response?.document);
const theme = useBuildTheme(response?.team?.customTheme, themeOverride);
React.useEffect(() => {

View File

@@ -71,7 +71,7 @@ function CommentThread({
document,
comment: thread,
});
const can = usePolicy(document.id);
const can = usePolicy(document);
const commentsInThread = comments
.inThread(thread.id)

View File

@@ -23,7 +23,7 @@ function Comments() {
const match = useRouteMatch<{ documentSlug: string }>();
const document = documents.getByUrl(match.params.documentSlug);
const focusedComment = useFocusedComment();
const can = usePolicy(document?.id);
const can = usePolicy(document);
useKeyDown("Escape", () => document && ui.collapseComments(document?.id));

View File

@@ -78,7 +78,7 @@ function DataLoader({ match, children }: Props) {
const isEditRoute =
match.path === matchDocumentEdit || match.path.startsWith(settingsPath());
const isEditing = isEditRoute || !user?.separateEditMode;
const can = usePolicy(document?.id);
const can = usePolicy(document);
const location = useLocation<LocationState>();
React.useEffect(() => {

View File

@@ -32,7 +32,7 @@ function TitleDocumentMeta({ to, document, revision, ...rest }: Props) {
const totalViewers = documentViews.length;
const onlyYou = totalViewers === 1 && documentViews[0].userId;
const viewsLoadedOnMount = React.useRef(totalViewers > 0);
const can = usePolicy(document.id);
const can = usePolicy(document);
const Wrapper = viewsLoadedOnMount.current ? React.Fragment : Fade;

View File

@@ -73,7 +73,6 @@ const DocumentTitle = React.forwardRef(function _DocumentTitle(
const ref = React.useRef<RefHandle>(null);
const [emojiPickerIsOpen, handleOpen, handleClose] = useBoolean();
const { editor } = useDocumentContext();
const can = usePolicy(documentId);
const handleClick = React.useCallback(() => {

View File

@@ -78,7 +78,7 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
multiplayer,
...rest
} = props;
const can = usePolicy(document.id);
const can = usePolicy(document);
const childRef = React.useRef<HTMLDivElement>(null);
const focusAtStart = React.useCallback(() => {

View File

@@ -111,7 +111,7 @@ function DocumentHeader({
});
const { isDeleted, isTemplate } = document;
const can = usePolicy(document?.id);
const can = usePolicy(document);
const canToggleEmbeds = team?.documentEmbeds;
const toc = (
<Tooltip

View File

@@ -58,7 +58,7 @@ function SharePopover({
const [urlSlug, setUrlSlug] = React.useState("");
const timeout = React.useRef<ReturnType<typeof setTimeout>>();
const buttonRef = React.useRef<HTMLButtonElement>(null);
const can = usePolicy(share ? share.id : "");
const can = usePolicy(share);
const documentAbilities = usePolicy(document);
const collection = document.collectionId
? collections.get(document.collectionId)