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

Added monitoring page with tabs as part of ODC-2571 #3837

Merged
merged 5 commits into from Jan 8, 2020

Conversation

abhi-kn
Copy link
Contributor

@abhi-kn abhi-kn commented Dec 27, 2019

US: https://issues.redhat.com/browse/ODC-2571

Analysis:
Need to add new menu item 'Monitoring' with tabs 'Dashboard' & 'Metrics'.

Solution:

  • Added new menu item Monitoring along with routes in console/plugin
  • created Monitoring page with tabs
  • created new components for Dashboard & Metrics page
  • added unit tests

Screen shots for design review:
@openshift/team-devconsole-ux
Screenshot 2019-12-28 at 12 40 51 AM

@openshift-ci-robot openshift-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Dec 27, 2019
@openshift-ci-robot openshift-ci-robot added the component/dev-console Related to dev-console label Dec 27, 2019
@abhi-kn
Copy link
Contributor Author

abhi-kn commented Dec 27, 2019

/assign @rohitkrai03

import * as React from 'react';
import { Helmet } from 'react-helmet';

const MonitoringDashboard: React.FC<{}> = () => {
Copy link
Member

Choose a reason for hiding this comment

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

nit : can we just have React.FC instead of React.FC<{}>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

);
};

export default useActiveNamespace(MonitoringPage);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: use in front of a name is usually used for custom hooks. You can probably name it something else like connectActiveNamespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Renamed as suggested

component: MonitoringMetrics,
},
]}
className={`co-m-${_.get(ProjectModel.kind, 'kind', ProjectModel.kind)}`}
Copy link
Contributor

Choose a reason for hiding this comment

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

This _.get method will always give undefined and will fall back to the default parameter every time. Also, no need to use get for models as they have hardcoded values

Suggested change
className={`co-m-${_.get(ProjectModel.kind, 'kind', ProjectModel.kind)}`}
className={`co-m-${ProjectModel.kind}`}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

Copy link
Contributor

@sahil143 sahil143 left a comment

Choose a reason for hiding this comment

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

Selecting namespace form the project selector doesn't update the URL. Similarly, changing the namespace in the URL doesn't update the selected item in the project selector.

properties: {
perspective: 'dev',
exact: false,
path: ['/dev/monitoring/all-namespaces', '/dev/monitoring/ns/:ns'],
Copy link
Member

Choose a reason for hiding this comment

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

Selecting namespace form the project selector doesn't update the URL. Similarly, changing the namespace in the URL doesn't update the selected item in the project selector.

This issue is happening because of dev in the path. Do we need dev in the path?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think dev has been added in the path because there's already a Monitoring Nav item present in the admin perspective (which has Alerting, Metrics & Dashboards as sub-items).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As @debsmita1 mentioned, I had added dev in path to differentiate Monitoring Nav in Admin & Dev perspective. But having /dev in path doesn't update the URL on namespace change & hence fixed the bug by changing path with dev prefix.

type: 'Page/Route',
properties: {
perspective: 'dev',
exact: false,
Copy link
Member

Choose a reason for hiding this comment

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

Exact should be true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having exact as true, Tab routes doesn't work & we get 404. Need to check with @christianvogt regarding this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I had similar issues with Project Details page. Christian and I opted out of using false as it can lead to unintentional matches. Since this is a new route, I am unsure it'll be of a big deal either way.

If you want an example of what I did, see:

  {
    type: 'Page/Route',
    properties: {
      perspective: 'dev',
      exact: true,
      path: ['/k8s/cluster/projects'],
      loader: async () =>
        (await import(
          './components/projects/details/AllProjectsDetailList' /* webpackChunkName: "all-projects-detail-list" */
        )).default,
    },
  },
  {
    type: 'Page/Route',
    properties: {
      perspective: 'dev',
      path: ['/k8s/cluster/projects/:name'],
      loader: async () =>
        (await import(
          './components/projects/details/ProjectDetailsPage' /* webpackChunkName: "project-details" */
        )).default,
    },
  },

In this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@andrewballantyne actually exact defaults to false so now I've split the routes & followed Project Details page approach.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, right you are... it was late when I made that comment :)

Copy link
Contributor

@sahil143 sahil143 left a comment

Choose a reason for hiding this comment

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

/lgtm

@openshift-ci-robot openshift-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Jan 2, 2020
@andrewballantyne
Copy link
Contributor

/kind feature

@openshift-ci-robot openshift-ci-robot added the kind/feature Categorizes issue or PR as related to a new feature. label Jan 2, 2020
Copy link
Contributor

@andrewballantyne andrewballantyne left a comment

Choose a reason for hiding this comment

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

/assign

);
};

export default connectActiveNamespace(MonitoringPage);
Copy link
Contributor

Choose a reason for hiding this comment

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

Root-level pages don't need to connect to the store to get the namespace. Each page, using the type: 'Page/Route' plugin, will be created using React-Router. This injects a match prop. Inside it is the namespace.

props.match.params
{ns: "<my-current-namespace>"}

The ns is the param you gave to the route URL in the plugin file '/dev-monitoring/ns/:ns'.

Checking for all-projects becomes a check against the missing param.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@andrewballantyne removed connection to store & now using the match prop to get namespace

Comment on lines 13 to 21
export const connectActiveNamespace = (Component) =>
connect((state: RootState) => ({ activeNamespace: getActiveNamespace(state) }))(Component);

export type ConnectActiveNamespaceProps = {
activeNamespace: string;
match: RMatch<{
ns?: string;
}>;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on my other comment, this isn't needed.

type: 'Page/Route',
properties: {
perspective: 'dev',
exact: false,
Copy link
Contributor

Choose a reason for hiding this comment

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

I had similar issues with Project Details page. Christian and I opted out of using false as it can lead to unintentional matches. Since this is a new route, I am unsure it'll be of a big deal either way.

If you want an example of what I did, see:

  {
    type: 'Page/Route',
    properties: {
      perspective: 'dev',
      exact: true,
      path: ['/k8s/cluster/projects'],
      loader: async () =>
        (await import(
          './components/projects/details/AllProjectsDetailList' /* webpackChunkName: "all-projects-detail-list" */
        )).default,
    },
  },
  {
    type: 'Page/Route',
    properties: {
      perspective: 'dev',
      path: ['/k8s/cluster/projects/:name'],
      loader: async () =>
        (await import(
          './components/projects/details/ProjectDetailsPage' /* webpackChunkName: "project-details" */
        )).default,
    },
  },

In this file.

@openshift-ci-robot openshift-ci-robot removed the lgtm Indicates that a PR is ready to be merged. label Jan 3, 2020
@abhi-kn
Copy link
Contributor Author

abhi-kn commented Jan 3, 2020

/retest

history.push(redirectURI);
};

export const MonitoringPage: React.FC<MonitoringPageProps> = (props) => {
Copy link
Member

Choose a reason for hiding this comment

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

nit : we can destructure props to { match }

};

export const MonitoringPage: React.FC<MonitoringPageProps> = (props) => {
const activeNamespace = _.get(props, 'match.params.ns');
Copy link
Member

Choose a reason for hiding this comment

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

and avoid _.get with const activeNamespace = match.params.ns;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

match.params will be an empty object when in all namespace so to avoid additional checks I've used _.get

Copy link
Member

Choose a reason for hiding this comment

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

yeah doesn't matter much just nit and as even for _.get we are not passing any default value so will be undefined for all namespace and same will be for match.params.ns as params is {}

Copy link
Contributor

Choose a reason for hiding this comment

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

We want to avoid using _.get especially when the type itself is defined in a way that disallows intermediate undefined values as is the case here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Avoided _.get

@invincibleJai
Copy link
Member

Have verified locally works as expected.

Note: user will see blank pages for dashboard and metrics tab as those are the separate US

@abhi-kn
Copy link
Contributor Author

abhi-kn commented Jan 3, 2020

/retest

@invincibleJai
Copy link
Member

/lgtm

@openshift-ci-robot openshift-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Jan 3, 2020
Copy link
Contributor

@andrewballantyne andrewballantyne left a comment

Choose a reason for hiding this comment

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

I'm not sure this new plugin config is good. I think you went half-way between what you had and what I previously did for the Project Details page. Recommend you go back to the way you had it or talk to Christian about alternatives tomorrow.

Comment on lines 518 to 529
{
type: 'Page/Route',
properties: {
perspective: 'dev',
exact: true,
path: ['/dev-monitoring/all-namespaces'],
loader: async () =>
(await import(
'./components/monitoring/MonitoringPage' /* webpackChunkName: "dev-console-monitoring" */
)).default,
},
},
{
type: 'Page/Route',
properties: {
perspective: 'dev',
path: ['/dev-monitoring/ns/:ns'],
loader: async () =>
(await import(
'./components/monitoring/MonitoringPage' /* webpackChunkName: "dev-console-monitoring" */
)).default,
},
},
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is appropriate. Two different routes configs to the same component? I think we should either do one for the "all project" list and one for the "Details" or just house them both under the non-exact config you had before. This is not helping much.

Perhaps @christianvogt knows of a better use of the plugin to get around the previous issue you had @abhi-kn - would recommend reaching out to him tomorrow.

Copy link
Contributor

Choose a reason for hiding this comment

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

Technically this is correct.
But I think you can create a single path as follows with exact: false:

/dev-monitoring(/all-namespaces$|/ns/:ns)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tried with suggested path & it works partially. Basically :ns doesn't work so I've reverted to single route with multiple paths.

};

export const MonitoringPage: React.FC<MonitoringPageProps> = (props) => {
const activeNamespace = _.get(props, 'match.params.ns');
Copy link
Contributor

Choose a reason for hiding this comment

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

We want to avoid using _.get especially when the type itself is defined in a way that disallows intermediate undefined values as is the case here.

<NamespacedPage
hideApplications
variant={NamespacedPageVariants.light}
onNamespaceChange={handleNamespaceChange}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this needed?
Isn't this performing the standard behavior of switching between namespaces?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without this URL doesn't get updated on selecting a project from Project List

export const MONITORING_NS_PAGE_URI = '/dev-monitoring/ns';
export const MONITORING_ALL_NS_PAGE_URI = '/dev-monitoring/all-namespaces';

export interface MonitoringPageProps {
Copy link
Contributor

Choose a reason for hiding this comment

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

We are no longer exporting component props.
Instead, when needed, a consumer can use React.ComponentProps<typeof MyComponent> to get a reference to the prop type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Removed export

Comment on lines 7 to 8
type MonitoringPageProps = React.ComponentProps<typeof MonitoringPage>;
let monPageProps: MonitoringPageProps;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not much value had separating the type definition like this.
May just as well do:
let monPageProps: React.ComponentProps<typeof MonitoringPage>

Also, it's more clear to move the declaration into the test itself or at the very least inside the describe block.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Modified as suggested.

};

const component = shallow(<MonitoringPage {...monPageProps} />);
expect(component.exists()).toBe(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

You can remove this assertion.
An assertion that the wrapper itself exists is useless because it will never be false.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed

};
const component = shallow(<MonitoringPage {...monPageProps} />);
expect(component.find(ProjectListPage).exists()).toBe(true);
expect(component.find(ProjectListPage).prop('title')).toBe('Monitoring');
Copy link
Contributor

Choose a reason for hiding this comment

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

This asserts static content.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, remove assertion.

describe('Monitoring Dashboard Tab', () => {
it('should render Monitoring Dashboard tab', () => {
const wrapper = shallow(<MonitoringDashboard />);
expect(wrapper.exists()).toBe(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Wrapper will always exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

it('should render Monitoring Dashboard tab', () => {
const wrapper = shallow(<MonitoringDashboard />);
expect(wrapper.exists()).toBe(true);
expect(wrapper.contains(<title>Dashboard</title>)).toBe(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Asserts static content.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a dummy test case as the page is empty now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure this is helpful... test file creation isn't a complicated process. Would recommend deleting this file.

expect(wrapper.exists()).toBe(true);
expect(wrapper.contains(<title>Metrics</title>)).toBe(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comments as before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a dummy test cases as the page is empty now

Comment on lines 518 to 529
{
type: 'Page/Route',
properties: {
perspective: 'dev',
exact: true,
path: ['/dev-monitoring/all-namespaces'],
loader: async () =>
(await import(
'./components/monitoring/MonitoringPage' /* webpackChunkName: "dev-console-monitoring" */
)).default,
},
},
{
type: 'Page/Route',
properties: {
perspective: 'dev',
path: ['/dev-monitoring/ns/:ns'],
loader: async () =>
(await import(
'./components/monitoring/MonitoringPage' /* webpackChunkName: "dev-console-monitoring" */
)).default,
},
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Technically this is correct.
But I think you can create a single path as follows with exact: false:

/dev-monitoring(/all-namespaces$|/ns/:ns)

@openshift-ci-robot openshift-ci-robot removed the lgtm Indicates that a PR is ready to be merged. label Jan 7, 2020
const component = shallow(<MonitoringPage {...monPageProps} />);
expect(component.exists()).toBe(true);
expect(component.find(PageHeading).exists()).toBe(true);
expect(component.find(PageHeading).prop('title')).toBe('Monitoring');
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think Unit Tests serve to validate acceptance criteria. They serve to help avoid accidental breaks.

};
const component = shallow(<MonitoringPage {...monPageProps} />);
expect(component.find(ProjectListPage).exists()).toBe(true);
expect(component.find(ProjectListPage).prop('title')).toBe('Monitoring');
Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, remove assertion.

Comment on lines +43 to +45
const actualTabs = component
.find(HorizontalNav)
.prop('pages')
.map((page) => page.name);
expect(actualTabs).toEqual(expectedTabs);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't mind this static test so much as it validates the order, which is another flex point that could be broken by changes.

it('should render Monitoring Dashboard tab', () => {
const wrapper = shallow(<MonitoringDashboard />);
expect(wrapper.exists()).toBe(true);
expect(wrapper.contains(<title>Dashboard</title>)).toBe(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure this is helpful... test file creation isn't a complicated process. Would recommend deleting this file.

describe('Monitoring Metrics Tab', () => {
it('should render Monitoring Metrics tab', () => {
const wrapper = shallow(<MonitoringMetrics />);
expect(wrapper.contains(<title>Metrics</title>)).toBe(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, I'd drop this file too. They can be created later to handle any functionality that is added.

@andrewballantyne
Copy link
Contributor

/lgtm
/approve

@openshift-ci-robot openshift-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Jan 8, 2020
@openshift-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: abhi-kn, andrewballantyne, invincibleJai, sahil143

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci-robot openshift-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jan 8, 2020
@andrewballantyne
Copy link
Contributor

Unblocking the team by getting this in. I don't believe there was any pending issues outside of tests that needs to be addressed.

@abhi-kn I'd like to see these changes done to cleanup the tests, but I think this foundation work should get in first so the team can build upon it.

@openshift-merge-robot openshift-merge-robot merged commit 196d87b into openshift:master Jan 8, 2020
@spadgett spadgett added this to the v4.4 milestone Jan 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. component/dev-console Related to dev-console kind/feature Categorizes issue or PR as related to a new feature. lgtm Indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet