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

[App Search] Add final Analytics table components #89233

Merged
merged 17 commits into from
Jan 29, 2021

Conversation

cee-chen
Copy link
Member

@cee-chen cee-chen commented Jan 25, 2021

Summary

Adds the final Analytics tables and completes the queries views. Components added:

  • Specific views:
    • AnalyticsTable (+ one-offs: RecentQueriesTable, QueryClicksTable)
    • AnalyticsSearch (a search bar that sends users directly to the Query Detail view)
  • Supporting components:
    • InlineTagsList
    • AnalyticsSection

I hadn't thought this was going to be as much code as it ended up being (1k+ lines 💀) - I definitely recommend following along by-commit as I tried to break this up as granularly as possible.

Screenshots

Analytics overview:

overview

Empty state:

empty overview

Query detail view:

query detail

Empty state:

empty query detail

Queries view - e.g., Top Queries, Recent Queries:

top queries

recent queries

Checklist

- export query types so that new table components can use them
- reorganize type keys by their (upcoming) table column order, remove unused tags from document obj
- used for tags columns in all tables
- there's a lot of logic separated out into constants.tsx right now, I promise it will make more sense when the one-off tables get added
+ add 'view all' button links to overview tables
- Why is the API for this specific table so different? who knows, but it do be that way
@cee-chen cee-chen added Feature:Plugins release_note:skip Skip the PR/issue when compiling release notes v7.12.0 labels Jan 25, 2021
@cee-chen cee-chen requested review from JasonStoltz, byronhulcher and a team January 25, 2021 21:25
- Have analytics filter form be on its own row separate from page title
- Change AnalyticsSearch to stretch to full width + add placeholder text + match header gutter + remain one line on mobile
import { Query } from '../../types';

interface Props {
tags: Query['tags'];
Copy link
Member

Choose a reason for hiding this comment

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

This is fine, just asking out of curiosity. Why use Query['tags'] here instead of just string[].

Copy link
Member Author

@cee-chen cee-chen Jan 28, 2021

Choose a reason for hiding this comment

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

@byronhulcher got me on this habit recently! #88095 (comment)

I like the idea of it because it means if we ever change a type/data structure in the future we can update it in one place and all other places that we're just passing that same prop to get it automatically.

Copy link
Member

Choose a reason for hiding this comment

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

It's also more explicit about what specifically should be passed, which helps with understanding I think.

Copy link
Member

@JasonStoltz JasonStoltz left a comment

Choose a reason for hiding this comment

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

LGTM, no concerns

@JasonStoltz
Copy link
Member

@elasticmachine merge upstream

@elastic elastic deleted a comment from kibanamachine Jan 29, 2021
@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
enterpriseSearch 1172 1189 +17

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
enterpriseSearch 1.8MB 1.8MB +21.6KB

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

Copy link
Contributor

@byronhulcher byronhulcher left a comment

Choose a reason for hiding this comment

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

Some minor comments. This looks good though, and I think it's fine to merge as is.

</AnalyticsSection>
);

expect(wrapper.find('h2').text()).toEqual('Lorem ipsum');
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a big fan of using consts for pass-through values

Copy link
Member Author

@cee-chen cee-chen Jan 29, 2021

Choose a reason for hiding this comment

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

I'm not. I've mentioned this before, and will continue to do so, but I very strongly prefer using static output and not variables in our test assertions.

  1. Performance is typically a non-issue in tests. Developer understanding is paramount and (in my opinion) is the foremost goal when it comes to writing unit tests. Another developer should be able to read only your test file and fully understand what the source code does, why, and its edge cases.

  2. With that understanding in mind, tests should be written for human readability and not for either compilation time, abstraction, or ease of updating. If your source code changes, your tests should typically change as well, as should documentation.

Consider the developer experience of the following:

const expectedValue = 'supercalifragilisticexpialidocious';

// …

// … a bunch of lines between your abstracted var and the test

// …

it('has an expected value', () => {
  expect(output).toEqual(expectedValue);
});

You're asking a developer to either hold in their memory the expected value between its declaration and it being used or you're asking them to find/scroll up and down your file. My question is: why? Why have this extra intermediate step or friction instead of assertions that read clearly and explicitly as-is, i.e.:

expect(output).toEqual('supercalifragilisticexpialidocious');

Now, consider the following worse example (it's contrived, but hopefully illustrative):

describe('someFn', () => {
  it('capitalizes strings and appends the word YOLO', () => {
    const input = 'supercalifragilisticexpialidocious';
    expect(someFn(input)).toEqual(input.toUpperCase() + 'YOLO');
  });
});

I consider this a particularly bad antipattern because the developer is literally just rewriting the logic of the function. That is NOT the point of a unit test, it doesn't scale the more complex the logic gets, and worse, it does not aid developer understanding (you might as well just go read the source code instead of reading the test). Consider this static example:

describe('someFn', () => {
  it('capitalizes strings and appends the word YOLO', () => {
    expect(
      someFn('supercalifragilisticexpialidocious')
    ).toEqual(
      'SUPERCALIFRAGILISTICEXPIALIDOCIOUSYOLO'
    );
  });
});

This unit test instead provides a good example of expected input/output and in combination with the test description, clearly illustrates (instead of duplicating) the source code.

Just to wrap up, I'm not saying we should never use variables or abstractions in tests. We absolutely do so in places and times where it makes sense (typically when it doesn't hinder explicit developer understanding). But I will strongly push for starting with static assertions as a baseline and abstractions as exception and not a rule in our unit tests.

@@ -140,3 +157,11 @@ export const Analytics: React.FC = () => {
</AnalyticsLayout>
);
};

export const ViewAllButton: React.FC<{ to: string }> = ({ to }) => (
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it okay to write props directly inside the React.FC<>?

Copy link
Member Author

@cee-chen cee-chen Jan 29, 2021

Choose a reason for hiding this comment

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

I mean, it's okay in that Typescript isn't throwing an error on it and it's validating correctly :) It's not common in our current codebase but IMO this is essentially the same as doing this:

const someFn = ({ someObjValue }: { someObjValue: string }) => {}

as opposed to

interface SomeFn {
  someObjValue: string;
}
const someFn = ({ someObjValue }: SomeFn) => {}

They're both the same/valid Typescript, and depending on the complexity of the incoming params it's nicer to do an inline interface over pulling it out.

Comment on lines +7 to +14
.analyticsHeader {
flex-wrap: wrap;

&__filters.euiPageHeaderSection {
width: 100%;
margin: $euiSizeM 0;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Worth adding a note or something about why this custom CSS is applied (since we largely try to avoid it?)

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm a little on the fence about it, we haven't added any comments so far on our CSS or previously had a history of doing so, so I'd probably want to consider it from a whole-app POV and discuss the benefits of doing so. I think so far the understanding is custom CSS consists mostly of EUI/layout overrides and hopefully it's easy to see what the CSS actually accomplishes by unchecking/hiding the custom CSS in a live browser

@cee-chen
Copy link
Member Author

Thanks Jason & Byron for the thoughtful review - will merge this PR as-is, but definitely happy to continue any discussions or larger implications here or in the frontend channel!

@cee-chen cee-chen merged commit 4f6de5a into elastic:master Jan 29, 2021
@cee-chen cee-chen deleted the analytics-4 branch January 29, 2021 19:42
@kibanamachine
Copy link
Contributor

Looks like this PR has a backport PR but it still hasn't been merged. Please merge it ASAP to keep the branches relatively in sync.

@kibanamachine kibanamachine added the backport missing Added to PRs automatically when the are determined to be missing a backport. label Feb 2, 2021
cee-chen pushed a commit that referenced this pull request Feb 2, 2021
* Add new AnalyticsSection component

* Update views that use AnalyticsSection

* [Setup] Update types + final API logic data

- export query types so that new table components can use them
- reorganize type keys by their (upcoming) table column order, remove unused tags from document obj

* [Setup] Migrate InlineTagsList component

- used for tags columns in all tables

* Create basic AnalyticsTable component

- there's a lot of logic separated out into constants.tsx right now, I promise it will make more sense when the one-off tables get added

* Update all views that use AnalyticsTable

+ add 'view all' button links to overview tables

* Add RecentQueriesTable component

- Why is the API for this specific table so different? who knows, but it do be that way

* Update views with RecentQueryTable

* Add QueryClicksTable component to QueryDetails view

* Create AnalyticsSearch bar for queries subpages

* [Polish] Add some space to the bottom of analytics pages

* [Design feedback] Tweak header + search form layout

- Have analytics filter form be on its own row separate from page title
- Change AnalyticsSearch to stretch to full width + add placeholder text + match header gutter + remain one line on mobile

* [PR feedback] Type clarification

* [PR feedback] Clear mocks

* [PR suggestion] File rename

constants.tsx -> shared_columns.tsx

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
@kibanamachine kibanamachine removed the backport missing Added to PRs automatically when the are determined to be missing a backport. label Feb 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Plugins release_note:skip Skip the PR/issue when compiling release notes v7.12.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants