diff --git a/app/components/WebsocketProvider.tsx b/app/components/WebsocketProvider.tsx index d7cbbc8db..4c36b8c1e 100644 --- a/app/components/WebsocketProvider.tsx +++ b/app/components/WebsocketProvider.tsx @@ -188,7 +188,7 @@ class WebsocketProvider extends React.Component { err instanceof NotFoundError ) { documents.removeCollectionDocuments(collectionId); - memberships.removeCollectionMemberships(collectionId); + memberships.removeAll({ collectionId }); collections.remove(collectionId); return; } @@ -302,7 +302,7 @@ class WebsocketProvider extends React.Component { policies.remove(doc.id); }); documents.removeCollectionDocuments(collectionId); - memberships.removeCollectionMemberships(collectionId); + memberships.removeAll({ collectionId }); collections.remove(collectionId); }) ); @@ -398,7 +398,7 @@ class WebsocketProvider extends React.Component { err instanceof NotFoundError ) { collections.remove(event.collectionId); - memberships.revoke({ + memberships.removeAll({ userId: event.userId, collectionId: event.collectionId, }); @@ -408,7 +408,7 @@ class WebsocketProvider extends React.Component { documents.removeCollectionDocuments(event.collectionId); } else { - memberships.revoke({ + memberships.removeAll({ userId: event.userId, collectionId: event.collectionId, }); diff --git a/app/scenes/CollectionPermissions/index.tsx b/app/scenes/CollectionPermissions/index.tsx index f4bf41358..d1d6c44db 100644 --- a/app/scenes/CollectionPermissions/index.tsx +++ b/app/scenes/CollectionPermissions/index.tsx @@ -267,10 +267,10 @@ function CollectionPermissions({ collectionId }: Props) { handleRemoveGroup(group)} onUpdate={(permission) => handleUpdateGroup(group, permission)} /> @@ -286,7 +286,10 @@ function CollectionPermissions({ collectionId }: Props) { handleRemoveUser(item)} onUpdate={(permission) => handleUpdateUser(item, permission)} diff --git a/app/scenes/Document/components/DocumentMeta.tsx b/app/scenes/Document/components/DocumentMeta.tsx index 1c938d67b..9ef0fecd6 100644 --- a/app/scenes/Document/components/DocumentMeta.tsx +++ b/app/scenes/Document/components/DocumentMeta.tsx @@ -37,7 +37,7 @@ function TitleDocumentMeta({ to, document, revision, ...rest }: Props) { const Wrapper = viewsLoadedOnMount.current ? React.Fragment : Fade; const insightsPath = documentInsightsPath(document); - const commentsCount = comments.inDocument(document.id).length; + const commentsCount = comments.filter({ documentId: document.id }).length; return ( diff --git a/app/scenes/Document/components/History.tsx b/app/scenes/Document/components/History.tsx index fb85575a9..adbebc473 100644 --- a/app/scenes/Document/components/History.tsx +++ b/app/scenes/Document/components/History.tsx @@ -22,7 +22,7 @@ function History() { const document = documents.getByUrl(match.params.documentSlug); const eventsInDocument = document - ? events.inDocument(document.id) + ? events.filter({ documentId: document.id }) : EMPTY_ARRAY; const onCloseHistory = () => { diff --git a/app/stores/CollectionGroupMembershipsStore.ts b/app/stores/CollectionGroupMembershipsStore.ts index 2a76e8141..5c4f02111 100644 --- a/app/stores/CollectionGroupMembershipsStore.ts +++ b/app/stores/CollectionGroupMembershipsStore.ts @@ -71,32 +71,9 @@ export default class CollectionGroupMembershipsStore extends Store m.groupId === groupId && m.collectionId === collectionId - ); - if (membership) { - this.remove(membership.id); - } - } - - @action - removeCollectionMemberships = (collectionId: string) => { - this.data.forEach((membership, key) => { - if (membership.collectionId === collectionId) { - this.remove(key); - } + this.removeAll({ + collectionId, + groupId, }); - }; - - /** - * Find a collection group membership by collectionId and groupId - * - * @param collectionId The collection ID - * @param groupId The group ID - * @returns The collection group membership or undefined if not found. - */ - find = (collectionId: string, groupId: string) => - Array.from(this.data.values()).find( - (m) => m.groupId === groupId && m.collectionId === collectionId - ); + } } diff --git a/app/stores/CommentsStore.ts b/app/stores/CommentsStore.ts index 16e189693..3f378a24a 100644 --- a/app/stores/CommentsStore.ts +++ b/app/stores/CommentsStore.ts @@ -1,4 +1,3 @@ -import filter from "lodash/filter"; import orderBy from "lodash/orderBy"; import { action, computed } from "mobx"; import Comment from "~/models/Comment"; @@ -18,8 +17,9 @@ export default class CommentsStore extends Store { * @returns Array of comments */ threadsInDocument(documentId: string): Comment[] { - return this.inDocument(documentId).filter( - (comment) => !comment.parentCommentId + return this.filter( + (comment: Comment) => + comment.documentId === documentId && !comment.parentCommentId ); } @@ -30,26 +30,12 @@ export default class CommentsStore extends Store { * @returns Array of comments */ inThread(threadId: string): Comment[] { - return filter( - this.orderedData, - (comment) => + return this.filter( + (comment: Comment) => comment.parentCommentId === threadId || comment.id === threadId ); } - /** - * Returns a list of comments in a document. - * - * @param documentId ID of the document to get comments for - * @returns Array of comments - */ - inDocument(documentId: string): Comment[] { - return filter( - this.orderedData, - (comment) => comment.documentId === documentId - ); - } - @action setTyping({ commentId, diff --git a/app/stores/EventsStore.ts b/app/stores/EventsStore.ts index e3109d736..863e5d7ff 100644 --- a/app/stores/EventsStore.ts +++ b/app/stores/EventsStore.ts @@ -1,5 +1,4 @@ -import filter from "lodash/filter"; -import sortBy from "lodash/sortBy"; +import orderBy from "lodash/orderBy"; import { computed } from "mobx"; import Event from "~/models/Event"; import RootStore from "./RootStore"; @@ -14,10 +13,6 @@ export default class EventsStore extends Store { @computed get orderedData(): Event[] { - return sortBy(Array.from(this.data.values()), "createdAt").reverse(); - } - - inDocument(documentId: string): Event[] { - return filter(this.orderedData, (event) => event.documentId === documentId); + return orderBy(Array.from(this.data.values()), "createdAt", "asc"); } } diff --git a/app/stores/IntegrationsStore.ts b/app/stores/IntegrationsStore.ts index e1218192a..be14bf854 100644 --- a/app/stores/IntegrationsStore.ts +++ b/app/stores/IntegrationsStore.ts @@ -1,6 +1,4 @@ -import filter from "lodash/filter"; import { computed } from "mobx"; -import { IntegrationService } from "@shared/types"; import naturalSort from "@shared/utils/naturalSort"; import RootStore from "~/stores/RootStore"; import Store from "~/stores/base/Store"; @@ -15,13 +13,6 @@ class IntegrationsStore extends Store { get orderedData(): Integration[] { return naturalSort(Array.from(this.data.values()), "name"); } - - @computed - get slackIntegrations(): Integration[] { - return filter(this.orderedData, { - service: IntegrationService.Slack, - }); - } } export default IntegrationsStore; diff --git a/app/stores/MembershipsStore.ts b/app/stores/MembershipsStore.ts index 103f08c25..d532426b2 100644 --- a/app/stores/MembershipsStore.ts +++ b/app/stores/MembershipsStore.ts @@ -71,7 +71,7 @@ export default class MembershipsStore extends Store { id: collectionId, userId, }); - this.revoke({ userId, collectionId }); + this.removeAll({ userId, collectionId }); } @action @@ -82,30 +82,4 @@ export default class MembershipsStore extends Store { } }); }; - - @action - revoke = ({ - userId, - collectionId, - }: { - collectionId: string; - userId: string; - }) => { - const membership = this.find(collectionId, userId); - if (membership) { - this.remove(membership.id); - } - }; - - /** - * Find a collection user membership by collectionId and userId - * - * @param collectionId The collection ID - * @param userId The user ID - * @returns The collection user membership or undefined if not found. - */ - find = (collectionId: string, userId: string) => - Array.from(this.data.values()).find( - (m) => m.userId === userId && m.collectionId === collectionId - ); } diff --git a/app/stores/SharesStore.ts b/app/stores/SharesStore.ts index 8510811fc..3ad250fe3 100644 --- a/app/stores/SharesStore.ts +++ b/app/stores/SharesStore.ts @@ -2,7 +2,7 @@ import invariant from "invariant"; import filter from "lodash/filter"; import find from "lodash/find"; import isUndefined from "lodash/isUndefined"; -import sortBy from "lodash/sortBy"; +import orderBy from "lodash/orderBy"; import { action, computed } from "mobx"; import type { Required } from "utility-types"; import type { JSONObject } from "@shared/types"; @@ -26,7 +26,7 @@ export default class SharesStore extends Store { @computed get orderedData(): Share[] { - return sortBy(Array.from(this.data.values()), "createdAt").reverse(); + return orderBy(Array.from(this.data.values()), "createdAt", "asc"); } @computed diff --git a/app/stores/UsersStore.ts b/app/stores/UsersStore.ts index dad596522..6aca78b50 100644 --- a/app/stores/UsersStore.ts +++ b/app/stores/UsersStore.ts @@ -8,6 +8,15 @@ import { client } from "~/utils/ApiClient"; import RootStore from "./RootStore"; import Store, { RPCAction } from "./base/Store"; +type UserCounts = { + active: number; + admins: number; + all: number; + invited: number; + suspended: number; + viewers: number; +}; + export default class UsersStore extends Store { actions = [ RPCAction.Info, @@ -19,14 +28,7 @@ export default class UsersStore extends Store { ]; @observable - counts: { - active: number; - admins: number; - all: number; - invited: number; - suspended: number; - viewers: number; - } = { + counts: UserCounts = { active: 0, admins: 0, all: 0, @@ -159,8 +161,12 @@ export default class UsersStore extends Store { }); @action - fetchCounts = async (teamId: string): Promise => { - const res = await client.post(`/users.count`, { + fetchCounts = async ( + teamId: string + ): Promise<{ + counts: UserCounts; + }> => { + const res = await client.post(`/≈`, { teamId, }); invariant(res?.data, "Data should be available"); diff --git a/app/stores/base/Store.ts b/app/stores/base/Store.ts index 63053c947..db7c1cb4c 100644 --- a/app/stores/base/Store.ts +++ b/app/stores/base/Store.ts @@ -1,4 +1,7 @@ import invariant from "invariant"; +import type { ObjectIterateeCustom } from "lodash"; +import filter from "lodash/filter"; +import find from "lodash/find"; import flatten from "lodash/flatten"; import lowerFirst from "lodash/lowerFirst"; import orderBy from "lodash/orderBy"; @@ -72,9 +75,7 @@ export default abstract class Store { } addPolicies = (policies: Policy[]) => { - if (policies) { - policies.forEach((policy) => this.rootStore.policies.add(policy)); - } + policies?.forEach((policy) => this.rootStore.policies.add(policy)); }; @action @@ -130,6 +131,15 @@ export default abstract class Store { this.data.delete(id); } + /** + * Remove all items in the store that match the predicate. + * + * @param predicate A function that returns true if the item matches, or an object with the properties to match. + */ + removeAll = (predicate: Parameters[0]) => { + this.filter(predicate).forEach((item) => this.remove(item.id)); + }; + save(params: Properties, options: JSONObject = {}): Promise { const { isNew, ...rest } = options; if (isNew || !("id" in params) || !params.id) { @@ -138,6 +148,11 @@ export default abstract class Store { return this.update(params, rest); } + /** + * Get a single item from the store that matches the ID. + * + * @param id The ID of the item to get. + */ get(id: string): T | undefined { return this.data.get(id); } @@ -291,4 +306,22 @@ export default abstract class Store { get orderedData(): T[] { return orderBy(Array.from(this.data.values()), "createdAt", "desc"); } + + /** + * Find an item in the store matching the given predicate. + * + * @param predicate A function that returns true if the item matches, or an object with the properties to match. + */ + find = (predicate: ObjectIterateeCustom): T | undefined => + // @ts-expect-error not sure why T is incompatible + find(this.orderedData, predicate); + + /** + * Filter items in the store matching the given predicate. + * + * @param predicate A function that returns true if the item matches, or an object with the properties to match. + */ + filter = (predicate: ObjectIterateeCustom): T[] => + // @ts-expect-error not sure why T is incompatible + filter(this.orderedData, predicate); } diff --git a/plugins/slack/client/Settings.tsx b/plugins/slack/client/Settings.tsx index 14b13b80e..b8d33528c 100644 --- a/plugins/slack/client/Settings.tsx +++ b/plugins/slack/client/Settings.tsx @@ -1,9 +1,8 @@ -import find from "lodash/find"; import { observer } from "mobx-react"; import * as React from "react"; import { useTranslation, Trans } from "react-i18next"; import styled from "styled-components"; -import { IntegrationType } from "@shared/types"; +import { IntegrationService, IntegrationType } from "@shared/types"; import Collection from "~/models/Collection"; import Integration from "~/models/Integration"; import Button from "~/components/Button"; @@ -38,14 +37,15 @@ function Slack() { }); }, [collections, integrations]); - const commandIntegration = find( - integrations.slackIntegrations, - (i) => i.type === IntegrationType.Command - ); + const commandIntegration = integrations.find({ + type: IntegrationType.Command, + service: IntegrationService.Slack, + }); const groupedCollections = collections.orderedData .map<[Collection, Integration | undefined]>((collection) => { - const integration = find(integrations.slackIntegrations, { + const integration = integrations.find({ + service: IntegrationService.Slack, collectionId: collection.id, });