Skip to content

Commit 09f84e0

Browse files
authored
refactor related venues + event fetching for performance/efficiency (#1368)
* add fetchVenue + refactor fetchSovereignVenueId to use it + track previously checked venues to avoid circular references * add getVenueCollectionRef + getVenueRef * add fetchChildVenues * add rough implementation plan for fetchRelatedVenues * move everything from api/sovereignVenue into api/venue * Implements sovereign venue search; Implements all child venues search * refactor fetchAllChildVenues into fetchDirectChildVenues + fetchDescendantVenues * add FIRESTORE_QUERY_IN_ARRAY_MAX_ITEMS to settings * refactor fetchDirectChildVenues to handle multiple venueIds with 'chunked firestore in array' query pattern * rename fetchSovereignVenueId -> fetchSovereignVenue * refactor fetchSovereignVenue to take options prop with named keys * add maxDepth option to fetchSovereignVenue * refactor fetchDescendantVenues to make use of fetchDirectChildVenues's chunked query pattern * fix fetchDescendantVenues to properly recurse * ensure fetchDirectChildVenues returns quickly when no ids to check * refactor fetchDescendantVenues to take options prop + add maxDepth * add anyVenueWithIdConverter + refactor code to use it * rename useConnectRelatedVenues -> useLegacyConnectRelatedVenues * refactor useSovereignVenueId to accept venueId prop directly * refactor useSovereignVenueId to expose errorMsg from state * refactor useSovereignVenueId to wrap main code in useEffect * add useRelatedVenues * ensure react-hooks/exhaustive-deps correctly lints useAsync's deps array * ensure our default emptyArray has a stable signature * cleanup reactions context/provider pattern * rename hooks/useRelatedVenues.ts -> hooks/useRelatedVenues.tsx * refactor useRelatedVenues to use context/provider pattern * refactor RelatedVenuesProvider to make venueId optional * stub out usage of useLegacyConnectRelatedVenues in unused legacy LiveSchedule component * add findVenueInRelatedVenues to RelatedVenuesProvider * add sovereignVenue to RelatedVenuesProvider * refactor useRelatedVenues to split into useRelatedVenues / useRelatedVenuesContext + add currentVenue / parentVenue * refactor VenuePartygoers to accept venueId as a prop * add RelatedVenuesProvider to WithNavigationBar * refactor VenuePartygoers to replace useLegacyConnectRelatedVenues with useRelatedVenues * add RelatedVenuesProvider to TemplateWrapper * stub out the seemingly legacy SplashPage route as it doesn't have a venueId in scope anyway * fix infinite recursion bug in fetchDescendantVenues * allow createPerformanceTrace to accept a string as the traceName * add tracePromise to utils/performance * wrap fetchRelatedVenues with tracePromise in RelatedVenuesProvider * add asArray helper + use it to simplify our param conversions * add getVenueEventCollectionRef to api/events * add fetchVenueEvents / venueEventWithIdConverter to api/events * add fetchAllVenueEvents to api/events * add useVenueEvents hook * refactor ArtPiece to pass venue as prop + cleanup legacy default export * cleanup ArtPiece imports * refactor SchedulePageModal to accept venueId prop + fix usages * add relatedVenueIds to RelatedVenuesProvider * refactor SchedulePageModal to use useRelatedVenues + useVenueEvents * remove useLegacyConnectRelatedVenues + related selectors/types/etc * refactor PartyMap + related types to accept venue prop * wrap fetchSovereignVenue with tracePromise in useSovereignVenueId * use venueEventsSelector in VenueLandingPage * cleanup firestore connections/selectors in VenuePage to keep related together * fix all occurrences of updateTheme to use useEffect * add @debt comments to places that try and load the entire venues collection into memory * use orderedVenuesSelector in VenueList * add @debt comments to places that try to select the entire venues collection from memory * add @debt / @deprecated comments to venuesSelector / orderedVenuesSelector / makeVenueSelector * refactor UserProfileModal to accept venue prop * refactor stripePromise to use tracePromise * pass venue to SuspectedLocation as prop * replace fetchSovereignVenueId with useSovereignVenueId in ProfilePictureInput
1 parent 2cab793 commit 09f84e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+828
-554
lines changed

.eslintrc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@
5252
}
5353
],
5454
"react/prop-types": "off",
55-
"react/display-name": "warn"
55+
"react/display-name": "warn",
56+
// @see https://github.com/facebook/react/tree/master/packages/eslint-plugin-react-hooks#advanced-configuration
57+
"react-hooks/exhaustive-deps": [
58+
"warn",
59+
{
60+
"additionalHooks": "(useAsync)"
61+
}
62+
]
5663
}
5764
}

functions/stats.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const eventIsNow = (event, now) => {
1919
);
2020
};
2121

22+
// @debt This function will currently load all venues in firebase into memory.. not very efficient
2223
exports.getOnlineStats = functions.https.onCall(async () => {
2324
const now = new Date().getTime();
2425

@@ -70,6 +71,7 @@ exports.getAllEvents = functions.https.onCall(async () => {
7071
);
7172
});
7273

74+
// @debt This function will currently load all venues in firebase into memory.. not very efficient
7375
exports.getLiveAndFutureEvents = functions.https.onCall(async () => {
7476
try {
7577
const now = new Date().getTime();

scripts/get-events.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ initFirebaseAdminApp(projectId, {
4343
.join(",")
4444
);
4545

46+
// @debt This function will currently load all venues in firebase into memory.. not very efficient
4647
const firestoreVenues = await admin.firestore().collection("venues").get();
4748

4849
firestoreVenues.docs.forEach((doc) => {

scripts/get-venue-owners.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ initFirebaseAdminApp(projectId, {
3939
nextPageToken = pageToken;
4040
}
4141

42+
// @debt This function will currently load all venues in firebase into memory.. not very efficient
4243
const firestoreVenues = await admin.firestore().collection("venues").get();
4344
const venues = firestoreVenues.docs.filter(
4445
(doc) =>

scripts/get-venues.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ initFirebaseAdminApp(projectId, {
5858
.join(",")
5959
);
6060

61+
// @debt This function will currently load all venues in firebase into memory.. not very efficient
6162
const firestoreVenues = await admin.firestore().collection("venues").get();
6263

6364
firestoreVenues.docs.forEach((doc) => {

scripts/rescale-placement.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const dryRun = dryRunFlag === "true";
2525
initFirebaseAdminApp(projectId);
2626

2727
(async () => {
28+
// @debt This function will currently load all venues in firebase into memory.. not very efficient
2829
const firestoreVenues = await admin.firestore().collection("venues").get();
2930

3031
for (const doc of firestoreVenues.docs) {

src/api/events.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import firebase from "firebase/app";
2+
3+
import { VenueEvent } from "types/venues";
4+
5+
import { withId, WithId, withVenueId, WithVenueId } from "utils/id";
6+
import { asArray } from "utils/types";
7+
8+
import { getVenueRef } from "./venue";
9+
10+
export const getVenueEventCollectionRef = (venueId: string) =>
11+
getVenueRef(venueId).collection("events");
12+
13+
export const fetchVenueEvents = async (
14+
venueId: string
15+
): Promise<WithVenueId<WithId<VenueEvent>>[]> =>
16+
getVenueEventCollectionRef(venueId)
17+
.withConverter(venueEventWithIdConverter)
18+
.get()
19+
.then((docSnapshot) =>
20+
docSnapshot.docs.map((venueEvent) =>
21+
withVenueId(venueEvent.data(), venueId)
22+
)
23+
);
24+
25+
export const fetchAllVenueEvents = async (
26+
venueIdOrIds: string | string[]
27+
): Promise<WithVenueId<WithId<VenueEvent>>[]> =>
28+
Promise.all(asArray(venueIdOrIds).map(fetchVenueEvents)).then((result) =>
29+
result.flat()
30+
);
31+
32+
/**
33+
* Convert VenueEvent objects between the app/firestore formats (@debt:, including validation).
34+
*/
35+
export const venueEventWithIdConverter: firebase.firestore.FirestoreDataConverter<
36+
WithId<VenueEvent>
37+
> = {
38+
toFirestore: (
39+
venueEvent: WithId<VenueEvent>
40+
): firebase.firestore.DocumentData => {
41+
// @debt Properly check/validate this data
42+
// return VenueEventSchema.validateSync(venueEvent);
43+
44+
return venueEvent;
45+
},
46+
47+
fromFirestore: (
48+
snapshot: firebase.firestore.QueryDocumentSnapshot
49+
): WithId<VenueEvent> => {
50+
// @debt Properly check/validate this data rather than using 'as'
51+
// return withId(VenueEventSchema.validateSync(snapshot.data(), snapshot.id);
52+
53+
return withId(snapshot.data() as VenueEvent, snapshot.id);
54+
},
55+
};

src/api/sovereignVenue.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/api/venue.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import Bugsnag from "@bugsnag/js";
22
import firebase from "firebase/app";
3+
import { chunk } from "lodash";
4+
5+
import { FIRESTORE_QUERY_IN_ARRAY_MAX_ITEMS } from "settings";
6+
7+
import { AnyVenue } from "types/venues";
8+
9+
import { withId, WithId } from "utils/id";
10+
import { asArray } from "utils/types";
311

412
export const getVenueCollectionRef = () =>
513
firebase.firestore().collection("venues");
@@ -40,3 +48,144 @@ export const setVenueLiveStatus = async ({
4048
})
4149
.finally(onFinish);
4250
};
51+
52+
export interface FetchSovereignVenueOptions {
53+
previouslyCheckedVenueIds?: readonly string[];
54+
maxDepth?: number;
55+
}
56+
57+
export interface FetchSovereignVenueReturn {
58+
sovereignVenue: WithId<AnyVenue>;
59+
checkedVenueIds: readonly string[];
60+
}
61+
62+
export const fetchSovereignVenue = async (
63+
venueId: string,
64+
options?: FetchSovereignVenueOptions
65+
): Promise<FetchSovereignVenueReturn> => {
66+
const { previouslyCheckedVenueIds = [], maxDepth } = options ?? {};
67+
68+
const venue = await fetchVenue(venueId);
69+
70+
if (!venue) throw new Error(`The '${venueId}' venue doesn't exist`);
71+
72+
if (!venue.parentId)
73+
return {
74+
sovereignVenue: venue,
75+
checkedVenueIds: previouslyCheckedVenueIds,
76+
};
77+
78+
if (previouslyCheckedVenueIds.includes(venueId))
79+
throw new Error(
80+
`Circular reference detected. '${venueId}' has already been checked`
81+
);
82+
83+
if (maxDepth && maxDepth <= 0)
84+
throw new Error("Maximum depth reached before finding the sovereignVenue.");
85+
86+
return fetchSovereignVenue(venue.parentId, {
87+
...options,
88+
previouslyCheckedVenueIds: [...previouslyCheckedVenueIds, venueId],
89+
maxDepth: maxDepth ? maxDepth - 1 : undefined,
90+
});
91+
};
92+
93+
export const fetchVenue = async (
94+
venueId: string
95+
): Promise<WithId<AnyVenue> | undefined> =>
96+
getVenueRef(venueId)
97+
.withConverter(anyVenueWithIdConverter)
98+
.get()
99+
.then((docSnapshot) => docSnapshot.data());
100+
101+
export const fetchDirectChildVenues = async (
102+
venueIdOrIds: string | string[]
103+
): Promise<WithId<AnyVenue>[]> => {
104+
const venueIds = asArray(venueIdOrIds);
105+
106+
if (venueIds.length <= 0) return [];
107+
108+
return Promise.all(
109+
chunk(venueIds, FIRESTORE_QUERY_IN_ARRAY_MAX_ITEMS).map(
110+
async (venueIdsChunk: string[]) => {
111+
const childVenuesSnapshot = await getVenueCollectionRef()
112+
.where("parentId", "in", venueIdsChunk)
113+
.withConverter(anyVenueWithIdConverter)
114+
.get();
115+
116+
return childVenuesSnapshot.docs
117+
.filter((docSnapshot) => docSnapshot.exists)
118+
.map((docSnapshot) => docSnapshot.data());
119+
}
120+
)
121+
).then((result) => result.flat());
122+
};
123+
124+
export interface FetchDescendantVenuesOptions {
125+
maxDepth?: number;
126+
}
127+
128+
export const fetchDescendantVenues = async (
129+
venueIdOrIds: string | string[],
130+
options?: FetchDescendantVenuesOptions
131+
): Promise<WithId<AnyVenue>[]> => {
132+
const venueIds = asArray(venueIdOrIds);
133+
134+
const { maxDepth } = options ?? {};
135+
136+
if (venueIds.length <= 0) return [];
137+
138+
const directChildVenues: WithId<AnyVenue>[] = await fetchDirectChildVenues(
139+
venueIds
140+
);
141+
142+
if (maxDepth && maxDepth <= 0 && directChildVenues.length > 0)
143+
throw new Error(
144+
"Maximum depth reached before fetching all descendant venues."
145+
);
146+
147+
const descendantVenues: WithId<AnyVenue>[] = await fetchDescendantVenues(
148+
directChildVenues.map((venue) => venue.id),
149+
{
150+
...options,
151+
maxDepth: maxDepth ? maxDepth - 1 : undefined,
152+
}
153+
);
154+
155+
return [...directChildVenues, ...descendantVenues];
156+
};
157+
158+
export const fetchRelatedVenues = async (
159+
venueId: string
160+
): Promise<WithId<AnyVenue>[]> => {
161+
const { sovereignVenue } = await fetchSovereignVenue(venueId);
162+
163+
const descendantVenues = await fetchDescendantVenues(sovereignVenue.id);
164+
165+
return [sovereignVenue, ...descendantVenues];
166+
};
167+
168+
/**
169+
* Convert Venue objects between the app/firestore formats (@debt:, including validation).
170+
*/
171+
export const anyVenueWithIdConverter: firebase.firestore.FirestoreDataConverter<
172+
WithId<AnyVenue>
173+
> = {
174+
toFirestore: (
175+
anyVenue: WithId<AnyVenue>
176+
): firebase.firestore.DocumentData => {
177+
// @debt Properly check/validate this data
178+
// return AnyVenueSchema.validateSync(anyVenue);
179+
180+
return anyVenue;
181+
},
182+
183+
fromFirestore: (
184+
snapshot: firebase.firestore.QueryDocumentSnapshot
185+
): WithId<AnyVenue> => {
186+
// @debt Properly check/validate this data rather than using 'as'
187+
// return withId(AnyVenueSchema.validateSync(snapshot.data(), snapshot.id);
188+
189+
return withId(snapshot.data() as AnyVenue, snapshot.id);
190+
},
191+
};

src/components/molecules/LiveSchedule/LiveSchedule.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
11
import React, { FC, useCallback, useMemo } from "react";
22

3-
import { VenueEvent } from "types/venues";
3+
import { AnyVenue, VenueEvent } from "types/venues";
44

55
import { isEventLive } from "utils/event";
66
import { venueSelector } from "utils/selectors";
7-
import { WithVenueId } from "utils/id";
7+
import { WithId, WithVenueId } from "utils/id";
88

9-
import { useConnectRelatedVenues } from "hooks/useConnectRelatedVenues";
9+
// import { useLegacyConnectRelatedVenues } from "hooks/useRelatedVenues";
1010
import { useSelector } from "hooks/useSelector";
11-
import { useVenueId } from "hooks/useVenueId";
11+
// import { useVenueId } from "hooks/useVenueId";
1212

1313
import { EventDisplay } from "../EventDisplay";
1414

1515
import "./LiveSchedule.scss";
1616
import { hasElements } from "utils/types";
1717

18+
const emptyArray: never[] = [];
19+
1820
const LiveSchedule: FC = () => {
19-
const venueId = useVenueId();
21+
// const venueId = useVenueId();
2022
const currentVenue = useSelector(venueSelector);
21-
useConnectRelatedVenues({ venueId });
2223

23-
const { relatedVenueEvents, relatedVenues } = useConnectRelatedVenues({
24-
venueId,
25-
withEvents: true,
26-
});
24+
// @debt Stubbing out legacy code as this component isn't used anymore and is getting deleted in a different PR.
25+
// useLegacyConnectRelatedVenues({ venueId });
26+
//
27+
// const { relatedVenueEvents, relatedVenues } = useLegacyConnectRelatedVenues({
28+
// venueId,
29+
// withEvents: true,
30+
// });
31+
const relatedVenues: WithId<AnyVenue>[] = emptyArray;
32+
const relatedVenueEvents: WithVenueId<VenueEvent>[] = emptyArray;
2733

2834
const relatedVenueFor = useCallback(
2935
(event: WithVenueId<VenueEvent>) =>

src/components/molecules/NavBar/NavBar.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ const NavBar: React.FC<NavBarPropsType> = ({
201201
>
202202
{navbarTitle} <span className="schedule-text">Schedule</span>
203203
</div>
204-
<VenuePartygoers />
204+
<VenuePartygoers venueId={venueId} />
205205
</div>
206206

207207
{!user && <NavBarLogin />}
@@ -315,7 +315,10 @@ const NavBar: React.FC<NavBarPropsType> = ({
315315
}`}
316316
onClick={hideEventSchedule}
317317
>
318-
<SchedulePageModal isVisible={isEventScheduleVisible} />
318+
<SchedulePageModal
319+
venueId={venueId}
320+
isVisible={isEventScheduleVisible}
321+
/>
319322
</div>
320323

321324
{/* @debt Remove back button from Navbar */}

0 commit comments

Comments
 (0)