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
Conversation
/assign @rohitkrai03 |
import * as React from 'react'; | ||
import { Helmet } from 'react-helmet'; | ||
|
||
const MonitoringDashboard: React.FC<{}> = () => { |
There was a problem hiding this comment.
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<{}>
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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)}`} |
There was a problem hiding this comment.
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
className={`co-m-${_.get(ProjectModel.kind, 'kind', ProjectModel.kind)}`} | |
className={`co-m-${ProjectModel.kind}`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this 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'], |
There was a problem hiding this 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.
This issue is happening because of dev
in the path. Do we need dev
in the path?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exact should be true
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
/kind feature |
There was a problem hiding this 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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
export const connectActiveNamespace = (Component) => | ||
connect((state: RootState) => ({ activeNamespace: getActiveNamespace(state) }))(Component); | ||
|
||
export type ConnectActiveNamespaceProps = { | ||
activeNamespace: string; | ||
match: RMatch<{ | ||
ns?: string; | ||
}>; | ||
}; |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
/retest |
history.push(redirectURI); | ||
}; | ||
|
||
export const MonitoringPage: React.FC<MonitoringPageProps> = (props) => { |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 {}
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoided _.get
Have verified locally works as expected. Note: user will see blank pages for dashboard and metrics tab as those are the separate US |
/retest |
/lgtm |
There was a problem hiding this 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.
{ | ||
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, | ||
}, | ||
}, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Removed export
type MonitoringPageProps = React.ComponentProps<typeof MonitoringPage>; | ||
let monPageProps: MonitoringPageProps; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This asserts static content.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrapper will always exist.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asserts static content.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comments as before.
There was a problem hiding this comment.
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
{ | ||
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, | ||
}, | ||
}, |
There was a problem hiding this comment.
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)
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'); |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, remove assertion.
const actualTabs = component | ||
.find(HorizontalNav) | ||
.prop('pages') | ||
.map((page) => page.name); | ||
expect(actualTabs).toEqual(expectedTabs); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
/lgtm |
[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 |
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. |
US: https://issues.redhat.com/browse/ODC-2571
Analysis:
Need to add new menu item 'Monitoring' with tabs 'Dashboard' & 'Metrics'.
Solution:
Screen shots for design review:
@openshift/team-devconsole-ux