chore: Improve relationship loading, include policies (#6321)
Use model where available in usePolicy
This commit is contained in:
@@ -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]);
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -71,7 +71,7 @@ function CommentThread({
|
||||
document,
|
||||
comment: thread,
|
||||
});
|
||||
const can = usePolicy(document.id);
|
||||
const can = usePolicy(document);
|
||||
|
||||
const commentsInThread = comments
|
||||
.inThread(thread.id)
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user