Skip to content

Removes pathological queries; Creates linting rule to prevent raw useFirestoreConnect import#801

Merged
mike-lvov merged 13 commits into
stagingfrom
devalias-fix-cadams-doc-or-collection-queries
Jan 14, 2021
Merged

Removes pathological queries; Creates linting rule to prevent raw useFirestoreConnect import#801
mike-lvov merged 13 commits into
stagingfrom
devalias-fix-cadams-doc-or-collection-queries

Conversation

@0xdevalias

@0xdevalias 0xdevalias commented Nov 25, 2020

Copy link
Copy Markdown
Contributor

Background Context: The original PR (#784) was deployed in #795, which broke things in production, and was reverted in #797

Thoughts: @denisdimitrov originally had #780 up to solve the core of our priority-prod-fix concerns for an upcoming event. If that solves the immediate need, then maybe we should resurrect that and get it out sooner, which will triage that need, and then we can work on these fixes over a less risky timeframe.

This PR will fix the issues that were breaking the admin page/anything else it broke in production.

@0xdevalias 0xdevalias added the ❌ do-not-merge PR should not be merged. Ask the person who added it for more details if the reason isn't clear. label Nov 25, 2020
@0xdevalias 0xdevalias added the P0 - Emergency/Fire 🔥🔥🔥 Priority 0 - Emergency. Once reviewed/merged/tested, deploy straight to master/other prod envs label Nov 25, 2020
@0xdevalias

Copy link
Copy Markdown
Contributor Author

@thecadams Created this branch/draft PR to get visibility back on the board for this issue + link up it's context, but absolutely happy for you to run with it and make the fixes/etc from your original PR.

@0xdevalias 0xdevalias removed the P0 - Emergency/Fire 🔥🔥🔥 Priority 0 - Emergency. Once reviewed/merged/tested, deploy straight to master/other prod envs label Nov 25, 2020
@cadamsdotcom

Copy link
Copy Markdown
Contributor

@0xdevalias picking this up now

@0xdevalias 0xdevalias added 🐛 bug A bug, error, issue, problem, etc within the platform. 💻 eng-QoL Engineering Quality of Life (QoL). Scripts, bots, automations, etc; that ensure our standards labels Dec 8, 2020
@cadamsdotcom

Copy link
Copy Markdown
Contributor

@cadamsdotcom cadamsdotcom marked this pull request as ready for review December 14, 2020 09:00
Comment thread src/hooks/useFirestoreConnect.ts Outdated
Comment thread src/components/molecules/NavBar/NavBar.tsx Outdated
@mike-lvov mike-lvov changed the title Prevent pathological queries when doc is not specified - Fix WIP: [FIX] Prevent pathological queries when doc is not specified Jan 4, 2021
@mike-lvov mike-lvov changed the title WIP: [FIX] Prevent pathological queries when doc is not specified WIP: [IMPROVEMENT] Prevent pathological queries when doc is not specified Jan 4, 2021
Comment thread src/hooks/useFirestoreConnect.ts
@mike-lvov

Copy link
Copy Markdown
Contributor

After conducting extensive research, I couldn't find a way to distinguish not passing doc vs passing undefined explicitly

microsoft/TypeScript#13195

I have found a way to solve this issue by replacing all useParams for venueId with useVenueId and return string | null from useVenueId. I suggest completely removing explicit undefined from the codebase as it is an antipattern to my opinion. This PR can be used as an argument to support my opinion

Comment thread src/hooks/useConnectCurrentEvent.ts
Comment thread src/hooks/useConnectUserPurchaseHistory.ts
Comment thread src/hooks/useConnectCurrentVenue.ts
@mike-lvov

This comment has been minimized.

@mike-lvov

Copy link
Copy Markdown
Contributor

RE: #801 (comment)

Partially closes sparkletown/internal-sparkle-issues#2
Not sure if I should implement other recommendations from this ticket,
since we basically gain nothing in terms of linting/etc.
@0xdevalias would be glad to hear your approach on this

@mike-lvov When you say "since we basically gain nothing in terms of linting/etc", why do you feel we "gain nothing" from it? Even if there aren't any issues it would detect currently, the idea of linting is to ensure that undesirable patterns won't be introduced again in future, and to provide guidance to developers who may try to about what our 'correct' way of implementing it is.

As far as implementing the other recommendations on the ticket, see my comments in sparkletown/internal-sparkle-issues#2 (comment) I believe we should be able to clean up all of those aspects as well, and then we can add a few more eslint rules to ban their usage.

I think I conveyed my thoughts in an unclear way. I'm didn't mean that we don't need linting. I meant that replacing import firebase from "firebase/app" with useFirebase would not result in any additional type safety (as it was with useFirestore vs useSparkleFirestore). I think of other issues as low priority clean-ups.

@mike-lvov

Copy link
Copy Markdown
Contributor

@0xdevalias I think the problem we have been experiencing recently is a lot of unrelated changes happening in one PR. To avoid that, I would rather implement https://github.com/sparkletown/internal-sparkle-issues/issues/2 issues in another PR

@mike-lvov mike-lvov force-pushed the devalias-fix-cadams-doc-or-collection-queries branch from 228abeb to 97e96ce Compare January 13, 2021 14:07
Comment thread src/hooks/useConnectCurrentEvent.ts
Comment thread src/hooks/useConnectUserPurchaseHistory.ts
Comment thread src/hooks/useConnectCurrentVenue.ts
@0xdevalias

Copy link
Copy Markdown
Contributor Author

After conducting extensive research, I couldn't find a way to distinguish not passing doc vs passing undefined explicitly

microsoft/TypeScript#13195

I have found a way to solve this issue by replacing all useParams for venueId with useVenueId and return string | null from useVenueId.

I think using useVenueId instead of the 'raw' useParams instances for this pattern is a good idea. I'd like to know more about the arguments behind the string | null before we do that part though.

I suggest completely removing explicit undefined from the codebase as it is an antipattern to my opinion. This PR can be used as an argument to support my opinion

Can you please expand on this, and your reasoning for it? In what cases are you seeing 'explicit undefined' being used, and in what way do you feel like it could be done better? Why do you see null as a better option than it (assuming that is what you are saying here)?


The original PR that introduced this was #784 (which was reverted in #797 as it broke things in production, and then this current PR was bringing it back + fixes)

Unfortunately #784 (like many of our PR's, which is something we need to change) doesn't have a useful description to outline the intended changes within it, so we have to rely on memory/inferring from the code.

Pulling from the review comments on that PR:

#784 (comment)

@0xdevalias What's the intent of SparkleDocRFQSetting here compared to SparkleRFQSetting?

@thecadams It's to ensure that if your intention is to query for a single doc, you only query for that doc.

@0xdevalias From @thecadams on zoom (paraphrased): essentially we had times when a doc that we were supplying was undefined, and so we were loading the entire collection (because it's an optional param in the underlying useFirestoreConnect hook), when our intent was to load a single tem or bailout.

The relevant type-related code is at https://github.com/sparkletown/sparkle/pull/784/files#diff-254a400761084e5ebd89145b80d201db7f0fd1e2dff8684213ee5d1804ee886aR11-R45

export interface SparkleRFQuery extends ReduxFirestoreQuerySetting {
  doc?: never;
  where: WhereOptions | WhereOptions[];
  storeAs?: ValidFirestoreKeys;
}

export interface SparkleRFDocQuery extends ReduxFirestoreQuerySetting {
  doc: string;
  where?: never;
  storeAs?: ValidFirestoreKeys;
}

Inferring from this code (without actually reading deeply/playing with it), I believe the intent is that:

  • When you pass doc, you can't pass where (intended to access 1 specific document, so no need for filtering)
  • When you pass where, you can't pass doc (because you aren't accessing a single document, but filtering over a collection of them)

You can also see that useSparkeFirestoreConnect was deleted in this PR, in favour of the more generalised custom useFirestoreConnect implementation:

@mike-lvov

Copy link
Copy Markdown
Contributor

Inferring from this code (without actually reading deeply/playing with it), I believe the intent is that:

When you pass doc, you can't pass where (intended to access 1 specific document, so no need for filtering)
When you pass where, you can't pass doc (because you aren't accessing a single document, but filtering over a collection of them)

Even though I support this intent, it didn't seem to work right. The current implementation of this(null) works good because then you try to pass null there it doesn't like it. So a type string | null will not be accepted by TS. That is the main reasoning behind useVenueId change.

@0xdevalias

This comment has been minimized.

@0xdevalias

This comment has been minimized.

@0xdevalias

0xdevalias commented Jan 14, 2021

Copy link
Copy Markdown
Contributor Author

RE: #801 (comment)

@mike-lvov:
I think I conveyed my thoughts in an unclear way. I'm didn't mean that we don't need linting. I meant that replacing import firebase from "firebase/app" with useFirebase would not result in any additional type safety (as it was with useFirestore vs useSparkleFirestore). I think of other issues as low priority clean-ups.

That makes sense. I agree that they are lower priority. 👍🏻


RE: #801 (comment)

@mike-lvov:
@0xdevalias I think the problem we have been experiencing recently is a lot of unrelated changes happening in one PR. To avoid that, I would rather implement sparkletown/internal-sparkle-issues#2 issues in another PR

I'm happy for that to be how we approach it. 👍🏻

@0xdevalias 0xdevalias left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Includes requested changes (I can't use the official 'request changes' review mode as I opened the original PR)

Comment on lines +62 to +68
const chatQuery = useMemo(() => {
if (!hasElements(chatUserIds)) return undefined;
const chatQuery = useMemo<SparkleRFQConfig>(() => {
if (!hasElements(chatUserIds)) return;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this will have the same code effect (that undefined) will be returned, yet this change makes that less explicitly obvious. What was the intent behind this change?

const { venueId } = useParams();
const venueId = useVenueId();
const { user } = useUser();
const [currentTimestamp] = useState(Date.now() / 1000);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[OOS] This is going to set currentTimestamp the first time this hook is called, and then never updated it again I believe, which seems (from it's usage) like it probably isn't the desired intent.

I think in other places where we have wanted this sort of 'only change every x timeframe' pattern we have used an interval or similar to update the useState. That might be a useful solution here as well.

Comment thread src/hooks/useConnectRelatedVenues.ts Outdated
Comment on lines +154 to +155
const siblingVenuesQuery: SparkleRFQConfig | undefined = !!parentId
const siblingVenuesQuery: SparkleRFQConfig = !!parentId

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was undefined removed here?

Comment thread src/hooks/useConnectRelatedVenues.ts Outdated
Comment on lines +163 to +164
const subvenuesQuery: SparkleRFQConfig | undefined = !!venueId
const subvenuesQuery: SparkleRFQConfig = !!venueId

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was undefined removed here?

Comment thread src/hooks/useConnectRelatedVenues.ts Outdated

// Parent Events
const parentVenueEventsQuery: SparkleRFQConfig | undefined =
const parentVenueEventsQuery =

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was undefined removed here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mike-lvov can you add the explicit typing back here please

Comment thread src/pages/Admin/Admin.tsx Outdated
Comment on lines +65 to +69
import {
SparkleRFQConfig,
useFirestoreConnect,
} from "hooks/useFirestoreConnect";
import { useVenueId } from "hooks/useVenueId";

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort with other hooks/ imports

import { useSelector } from "hooks/useSelector";
import VenueEventDetails from "./VenueEventDetails";
import { useFirestoreConnect } from "react-redux-firebase";
import { useFirestoreConnect } from "hooks/useFirestoreConnect";

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort with other hooks/ imports

Comment on lines +44 to +45
if (!venueId) return history.replace("/admin");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we redirecting within this function? I assume the intent of this addition was to ensure venueId isn't undefined/similar by the time it gets passed to .doc(). Why wouldn't we just return early like we do with all of our other guard patterns like this?

Suggested change
if (!venueId) return history.replace("/admin");
if (!venueId) return

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that later on this same history.replace code is used, so that makes sense.

Though it seems we could move this guard outside of the async fetchVenueFromAPI function and just have it in the root of the useEffect?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, but it still would be the same

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats fine. I think it just more confused me being inside the inner function, rather than the root of the useEffect

Comment thread src/hooks/useConnectRelatedVenues.ts Outdated
Comment on lines +204 to +210
const allValidQueries: SparkleRFQConfig[] = [
const allValidQueries: SparkleRFQConfig = [

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels more confusing than how it used to be.. probably would have named your 'fusion' type as AnySparkleRFQConfig or something specific about the props the function accepts.

Alternatively, given the change has already been done, this would probably be more correct/easy to understand as SparkleRFQConfigObject[]. 'Hiding' the array'ness of it makes it confusing, and I need to go any look up the original 'fusion type' to understand whats going on.


import { ValidFirestoreKeys } from "types/Firestore";

/**

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defined<T> is already defined in src/types/utility.ts so doesn't need to exist here

@0xdevalias

0xdevalias commented Jan 14, 2021

Copy link
Copy Markdown
Contributor Author

RE: #801 (comment)

Even though I support this intent, it didn't seem to work right. The current implementation of this(null) works good because then you try to pass null there it doesn't like it. So a type string | null will not be accepted by TS. That is the main reasoning behind useVenueId change.

I'll open a sub-PR that fixes this properly shortly, just polishing it locally.

@mike-lvov See sub-PR #1076 for an implementation that fixes this properly, without needing to result to null hacks

@0xdevalias 0xdevalias left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skimming over the changed files I only had 1 small request, then i'm happy for this PR to be merged.

Since I was the one who originally opened it, I can't actually approve it. But once you make the change mentioned here, feel free to approve and merge it.

(When squash-merging, make sure to keep the 'co-authored by' text it adds)

Comment thread src/hooks/useConnectRelatedVenues.ts Outdated

// Parent Events
const parentVenueEventsQuery: SparkleRFQConfig | undefined =
const parentVenueEventsQuery =

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mike-lvov can you add the explicit typing back here please

Comment thread src/hooks/useVenueId.ts
Comment on lines +15 to +17
// @debt I think we could just use useParams() here instead of needing to loop through all of the venuePath, so long as the use :venueId in their path
// https://reactrouter.com/web/api/Hooks/useparams
// useParams returns an object of key/value pairs of URL parameters. Use it to access match.params of the current <Route>.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mike-lvov mike-lvov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@qlty-cloud-legacy

Copy link
Copy Markdown

Code Climate has analyzed commit ab6d832 and detected 6 issues on this pull request.

Here's the issue category breakdown:

Category Count
Duplication 6

View more on Code Climate.

@mike-lvov mike-lvov merged commit 24f697a into staging Jan 14, 2021
@mike-lvov mike-lvov deleted the devalias-fix-cadams-doc-or-collection-queries branch January 14, 2021 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants