Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: send request state to component, process data in components #101188

Merged
merged 2 commits into from
Apr 13, 2023

Conversation

xinhaoz
Copy link
Member

@xinhaoz xinhaoz commented Apr 10, 2023

We should not decouple the data from a fetch request and fields
describing the request/response like inFlight, valid,
lastUpdated which are stored in redux, when passing them as props
into the component.

In traditional redux apps we we would be able to select the request
state as a whole unit, getting fields like isLoading, error and
etc with the data portion of the payload. Unfortunately, with the
our split redux stores in db-console and CC, we were not able to
select directly from the stores. Instead, we have been following a
pattern of creating selectors for each required part of the request
field (e.g. selectData, selectError, selectDataIsValid) for
each cached request, having to create a copy of each selector in both
db-console and cluster-ui. These selectors are then used to pass each
field independently as a prop. One can see how this boilerplate code
can quickly multiply with the number of new requests being created.

This pattern was probably a mistake -- we use selectors to memoize
computations when we want to transform data from the redux store.
For modern React we have built-in utilities like useMemo that can
accomplish this within the component. For many fields currently using
createSelector, like isLoading, the logic is extremely simple, where
the field is directly read and returned with no computation. This means
caching with createSelectorin most cases is unnecessary, so we have
a lot of boilerplate with no useful effects. In addition, the processing
of some of the data in each prop selectors (db-console and cluster-ui)
means we have code that gets duplicated. This is hard to maintain and
can become confusing as the prop may even go through more processing
when inside the component.

We should adopt a pattern of sending the entire request state for cached
data fetches to the components reading them. This commit introduces the
RequestState<T>> type which going forward should be used as the redux
field type for all cached data fetches in cluster-ui, and be prop type
used by components that use the response.

This commit shows an example of what can be done with the statements page
and sql stats response when we adopt this pattern. Note that for class
components, where useMemo is not available, we can just use redux's
selectors/reselect to help memoize when necessary.

Release note: None


DB-Console: https://www.loom.com/share/2dbc66751fce49aea25b5f341d26e76a
CC: https://www.loom.com/share/3cd57711da2441eaab9fbf593607c03d

Epic: CRDB-25476

@cockroach-teamcity
Copy link
Member

This change is Reviewable

@xinhaoz xinhaoz force-pushed the props-selector-refactor branch 6 times, most recently from f6156a8 to 35b299e Compare April 12, 2023 19:02
@xinhaoz xinhaoz marked this pull request as ready for review April 12, 2023 19:10
@xinhaoz xinhaoz requested review from a team April 12, 2023 19:10
Copy link
Contributor

@maryliag maryliag left a comment

Choose a reason for hiding this comment

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

Nice changes!
I would like to see a loom with the page working on DB and CC

Reviewed 14 of 14 files at r1, 11 of 12 files at r2, all commit messages.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @xinhaoz)


pkg/ui/workspaces/db-console/src/redux/apiReducers.ts line 650 at r1 (raw file):

// This mapped type assigns keys in object type T where the key value
// is of type V to the key value. Otherwise, it assigns it 'never'.
// THis enables one to etract keys in an object T of type V.

nit: this
nit: extract?


pkg/ui/workspaces/db-console/src/views/statements/statements.spec.tsx line 58 at r2 (raw file):

  fixedWindowEnd: moment(1646334815),
};

why all these tests got removed?

Copy link
Contributor

@j82w j82w left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @maryliag and @xinhaoz)


pkg/ui/workspaces/cluster-ui/src/sqlActivity/util.tsx line 47 at r1 (raw file):

  const regions = filters.regions?.length > 0 ? filters.regions.split(",") : [];
  const nodes = filters.nodes?.length > 0 ? filters.nodes.split(",") : [];
  const appNames = filters.app

I don't see this being used anywhere. Am I missing something?


pkg/ui/workspaces/cluster-ui/src/sqlActivity/util.tsx line 144 at r1 (raw file):

      if (
        data.internal_app_name_prefix &&
        app.startsWith(data.internal_app_name_prefix)

Should there be a check if the app value exists? It checks below: apps.add(app ? app : unset);

We should not decouple the data from a fetch request and fields
describing the request/response like `inFlight`, `valid`,
`lastUpdated` which are stored in redux, when passing them as props
into the component.

In traditional redux apps we we would be able to select the request
state as a whole unit, getting fields like `isLoading`, `error` and
etc with the data portion of the payload.  Unfortunately, with the
our split redux stores in db-console and CC, we were not able to
select directly from the stores.  Instead, we have been following a
pattern of creating selectors for each required part of the request
field (e.g. `selectData`, `selectError`, `selectDataIsValid`) for
each cached request, having to create a copy of each selector in both
db-console and cluster-ui. These selectors are then used to pass each
field independently as a prop. One can see how this boilerplate code
can quickly multiply with the number of new requests being created.

This pattern was probably a mistake -- we use selectors to memoize
computations when we want to transform data from the redux store.
For modern React we have built-in utilities like `useMemo` that can
accomplish this within the component. For many fields currently using
`createSelector`, like `isLoading`, the logic is extremely simple, where
the field is directly read and returned with no computation. This means
caching with `createSelector`in most cases is unnecessary, so we have
a lot of boilerplate with no useful effects. In addition, the processing
of some of the data in each prop selectors (db-console and cluster-ui)
means we have code that gets duplicated.  This is hard to maintain and
can become confusing as the prop may even go through more processing
when inside the component.

We should adopt a pattern of sending the entire request state for cached
data fetches to the components reading them. This commit introduces the
`RequestState<T>>` type which going forward should be used as the redux
field type for all cached data fetches in cluster-ui, and be prop type
used by components that use the response.

This commit shows an example of what can be done with the statements page
and sql stats response when we adopt this pattern. Note that for class
components, where `useMemo` is not available, we can just use redux's
selectors/reselect to help memoize when necessary.

Epic: none

Release note: None
Copy link
Member Author

@xinhaoz xinhaoz left a comment

Choose a reason for hiding this comment

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

Loom added!

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @j82w and @maryliag)


pkg/ui/workspaces/cluster-ui/src/sqlActivity/util.tsx line 47 at r1 (raw file):

Previously, j82w (Jake) wrote…

I don't see this being used anywhere. Am I missing something?

Ah I think that was deleted by a merge conflict by accident (it's used in the next commit) - put it back for this commit as well.


pkg/ui/workspaces/cluster-ui/src/sqlActivity/util.tsx line 144 at r1 (raw file):

Previously, j82w (Jake) wrote…

Should there be a check if the app value exists? It checks below: apps.add(app ? app : unset);

Ah yes it might be a good idea to add that in. It's true we don't guarantee app is not null in key_data. I also added some tests for this case.


pkg/ui/workspaces/db-console/src/redux/apiReducers.ts line 650 at r1 (raw file):

Previously, maryliag (Marylia Gutierrez) wrote…

nit: this
nit: extract?

Done.


pkg/ui/workspaces/db-console/src/views/statements/statements.spec.tsx line 58 at r2 (raw file):

Previously, maryliag (Marylia Gutierrez) wrote…

why all these tests got removed?

We don't use these selectors anymore. The selectors were just wrappers around the functions in sqlActivity/util.tsx, which are now being tested in the corresponding test file (and moved to cluster-ui).

@xinhaoz xinhaoz requested review from a team, maryliag and j82w April 13, 2023 14:08
Copy link
Contributor

@maryliag maryliag left a comment

Choose a reason for hiding this comment

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

a few nits, otherwise
:lgtm:

Reviewed 12 of 12 files at r3, 12 of 12 files at r4, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @j82w and @xinhaoz)


pkg/ui/workspaces/cluster-ui/src/sqlActivity/util.spec.tsx line 231 at r4 (raw file):

    expect(
      filteredStmts.map(stmt => Number(stmt.aggregatedFingerprintID)).sort(),
    ).toEqual(expectedIDs);

all tests are using this very same code block (or some cases almost the same), so maybe you can create a function that receives: expectedCount, expectedIDs, stmtsRaw and the filters, so each test only have to call that function that will do all these checks

Code quote:

    const expectedCount = 3;
    const expectedIDs = [1, 2, 6];

    const statements = convertRawStmtsToAggregateStatistics(stmtsRaw);

    const filteredStmts = filteredStatementsData(
      filters,
      null,
      statements,
      false,
    );

    expect(filteredStmts.length).toEqual(expectedCount);

    expect(
      filteredStmts.map(stmt => Number(stmt.aggregatedFingerprintID)).sort(),
    ).toEqual(expectedIDs);

pkg/ui/workspaces/cluster-ui/src/sqlActivity/util.spec.tsx line 291 at r4 (raw file):

  it("should filter out statements with svc_lat less than filter time value", () => {
    // Create stmts with ids matching service_lat in seceonds.

nit: seconds

…rForCachedDataField`

Note that the `testUtils` file in the api folder is a forward port of
testing utilities created for cockroachdb#99403

This commit adds testing for functions in `sqlActivity/util.tsx` and
adds some checks to those functions to make them more safe to use.

The param `nodeRegions` is removed from `filteredStatementsData` as it
was unused.

Epic: none

Release note: None
@xinhaoz
Copy link
Member Author

xinhaoz commented Apr 13, 2023

Addressed feedback! TFTR!!
bors r+

@craig
Copy link
Contributor

craig bot commented Apr 13, 2023

Build succeeded:

@craig craig bot merged commit 8fbb38c into cockroachdb:master Apr 13, 2023
@xinhaoz xinhaoz deleted the props-selector-refactor branch June 5, 2023 17:04
xinhaoz added a commit to xinhaoz/cockroach that referenced this pull request Aug 21, 2023
The filter on app name = empty string was not working on
the stmts page. This was due to the fact that we use (unset)
as the option in the filter to represent selecting the empty
string app name. However when filtering statements, the empty
string app name on the stmt was not changed accordingly.
this commit fixes this and also adds testing for the unset case.

This commit also adds sql api testing functions to mock stmt
data, which was introduced in cockroachdb#101188.

Epic: none
Fixes: cockroachdb#107748

Release note (bug fix): Filter on stmts page works for
app name = empty string (represented as 'unset').
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants