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

Agent version warning #1259

Merged
merged 10 commits into from
Mar 16, 2023
9 changes: 5 additions & 4 deletions assets/js/components/Health/HealthIcon.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function HealthIcon({
health = undefined,
centered = false,
hoverOpacity = true,
size = 'l',
}) {
const hoverOpacityClass = {
'hover:opacity-75': hoverOpacity,
Expand All @@ -24,7 +25,7 @@ function HealthIcon({
case 'passing':
return (
<EOS_CHECK_CIRCLE_OUTLINED
size="l"
size={size}
className={classNames(
hoverOpacityClass,
computedIconCssClass('fill-jungle-green-500', centered)
Expand All @@ -34,7 +35,7 @@ function HealthIcon({
case 'warning':
return (
<EOS_WARNING_OUTLINED
size="l"
size={size}
className={classNames(
hoverOpacityClass,
computedIconCssClass('fill-yellow-500', centered)
Expand All @@ -44,7 +45,7 @@ function HealthIcon({
case 'critical':
return (
<EOS_ERROR_OUTLINED
size="l"
size={size}
className={classNames(
hoverOpacityClass,
computedIconCssClass('fill-red-500', centered)
Expand All @@ -56,7 +57,7 @@ function HealthIcon({
default:
return (
<EOS_LENS_FILLED
size="l"
size={size}
className={classNames(
hoverOpacityClass,
computedIconCssClass('fill-gray-500', centered)
Expand Down
31 changes: 31 additions & 0 deletions assets/js/components/Health/HealthIcon.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import HealthIcon from '.';

export default {
title: 'HealthIcon',
component: HealthIcon,
};

export function Passing() {
return <HealthIcon health="passing" />;
}

export function Warning() {
return <HealthIcon health="warning" />;
}

export function Critical() {
return <HealthIcon health="critical" />;
}

export function Pending() {
return <HealthIcon health="pending" />;
}

export function Default() {
return <HealthIcon health="unknown" />;
}

export function ExtraLarge() {
return <HealthIcon health="passing" size="xl" />;
}
6 changes: 6 additions & 0 deletions assets/js/components/Health/HealthIcon.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('HealthIcon', () => {
expect(svgEl.classList.toString()).toContain(
'hover:opacity-75 fill-jungle-green-500'
);
expect(svgEl).toHaveAttribute('width', '24');
});
it('should display a yellow svg when the health is warning', () => {
const { container } = render(<HealthIcon health="warning" />);
Expand All @@ -32,4 +33,9 @@ describe('HealthIcon', () => {
const svgEl = container.querySelector("[data-testid='eos-svg-component']");
expect(svgEl.classList.toString()).toContain('hover:opacity-100');
});
it('should display the icon with the applied size', () => {
const { container } = render(<HealthIcon health="passing" size="m" />);
const svgEl = container.querySelector("[data-testid='eos-svg-component']");
expect(svgEl).toHaveAttribute('width', '18');
});
arbulu89 marked this conversation as resolved.
Show resolved Hide resolved
});
7 changes: 7 additions & 0 deletions assets/js/components/HostDetails/HostDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { useParams } from 'react-router-dom';
import { useSelector } from 'react-redux';

import { networkClient } from '@lib/network';
import { agentVersionWarning } from '@lib/agent';

import ListView from '@components/ListView';
import Table from '@components/Table';

import PageHeader from '@components/PageHeader';
import BackButton from '@components/BackButton';
import ClusterLink from '@components/ClusterLink';
import WarningBanner from '@components/Banners/WarningBanner';
import SuseLogo from '@static/suse_logo.svg';
import {
getInstancesOnHost,
Expand Down Expand Up @@ -50,6 +52,8 @@ function HostDetails() {
return <div>Not Found</div>;
}

const versionWarningMessage = agentVersionWarning(host.agent_version);

return (
<div>
<BackButton url="/hosts">Back to Hosts</BackButton>
Expand All @@ -76,6 +80,9 @@ function HostDetails() {
)
)}
</div>
{versionWarningMessage && (
<WarningBanner>{versionWarningMessage}</WarningBanner>
)}
<div className="mt-4 bg-white shadow rounded-lg py-4 px-8">
<ListView
orientation="vertical"
Expand Down
69 changes: 69 additions & 0 deletions assets/js/components/HostDetails/HostDetails.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react';
import { screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import MockAdapter from 'axios-mock-adapter';

import {
withState,
defaultInitialState,
renderWithRouterMatch,
} from '@lib/test-utils';
import { hostFactory } from '@lib/test-utils/factories';
import { networkClient } from '@lib/network';

import HostDetails from './HostDetails';

// delayResponse is need to avoid component updates on axios requests
const axiosMock = new MockAdapter(networkClient, { delayResponse: 2000 });

describe('HostDetails component', () => {
beforeEach(() => {
axiosMock.reset();
});

it('should not show any warning message if the agent version is correct', () => {
const hosts = hostFactory.buildList(1, { agent_version: '2.0.0' });
const { id: hostID } = hosts[0];
const state = {
...defaultInitialState,
hostsList: {
hosts,
},
};
const [StatefulHostDetails] = withState(<HostDetails />, state);

renderWithRouterMatch(StatefulHostDetails, {
path: '/hosts/:hostID',
route: `/hosts/${hostID}`,
});

expect(
screen.queryByText(
'Agent version 2.0.0 or greater is required for the new checks engine.'
)
).not.toBeInTheDocument();
});

it('should show 2.0.0 version required warning message', () => {
const hosts = hostFactory.buildList(1, { agent_version: '1.0.0' });
const { id: hostID } = hosts[0];
const state = {
...defaultInitialState,
hostsList: {
hosts,
},
};
const [StatefulHostDetails] = withState(<HostDetails />, state);

renderWithRouterMatch(StatefulHostDetails, {
path: '/hosts/:hostID',
route: `/hosts/${hostID}`,
});

expect(
screen.getByText(
'Agent version 2.0.0 or greater is required for the new checks engine.'
)
).toBeInTheDocument();
});
});
50 changes: 39 additions & 11 deletions assets/js/components/HostsList.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import React, { Fragment } from 'react';
import React from 'react';

import { useSearchParams } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { EOS_WARNING_OUTLINED } from 'eos-icons-react';

import Table from '@components/Table';
import HealthIcon from '@components/Health/HealthIcon';
import { useSearchParams } from 'react-router-dom';
import Tags from '@components/Tags';
import { addTagToHost, removeTagFromHost } from '@state/hosts';
import HostLink from '@components/HostLink';
import ClusterLink from '@components/ClusterLink';
import SapSystemLink from '@components/SapSystemLink';
import PageHeader from '@components/PageHeader';
import { useSelector, useDispatch } from 'react-redux';

import { post, del } from '@lib/network';
import Pill from '@components/Pill';
import HealthSummary from '@components/HealthSummary/HealthSummary';
import { getCounters } from '@components/HealthSummary/summarySelection';
import ProviderLabel from '@components/ProviderLabel';
import Tooltip from '@components/Tooltip';

import { addTagToHost, removeTagFromHost } from '@state/hosts';
import { post, del } from '@lib/network';
import { agentVersionWarning } from '@lib/agent';

const getInstancesByHost = (applicationInstances, databaseInstances, hostId) =>
applicationInstances
Expand Down Expand Up @@ -118,11 +124,33 @@ function HostsList() {
{
title: 'Agent version',
key: 'agent_version',
render: (content) => (
<span className="px-2 text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800 truncate w-32 inline-block">
{content}
</span>
),
render: (content) => {
const warning = agentVersionWarning(content);
if (warning) {
return (
<Pill
size="xs"
className="bg-yellow-100 text-yellow-800 group flex items-center relative"
>
<EOS_WARNING_OUTLINED
size="base"
className="centered fill-yellow-800"
/>
<span className="ml-1 truncate max-w-[100px]">{content}</span>
<Tooltip tooltipText={warning} width="w-52 -translate-x-1/3" />
</Pill>
);
}
return (
<Pill
size="xs"
display="inline-block"
className="bg-green-100 text-green-800 truncate max-w-[112px]"
>
{content}
</Pill>
);
},
},
{
title: 'Tags',
Expand Down
49 changes: 48 additions & 1 deletion assets/js/components/HostsList.test.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import React from 'react';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import 'intersection-observer';
import '@testing-library/jest-dom';
import { hostFactory } from '@lib/test-utils/factories';

import { renderWithRouter, withDefaultState, withState } from '@lib/test-utils';
import {
renderWithRouter,
withDefaultState,
withState,
defaultInitialState,
} from '@lib/test-utils';

import { filterTable, clearFilter } from '@components/Table/Table.test';

Expand Down Expand Up @@ -50,6 +56,47 @@ describe('HostsLists component', () => {
);
});
});

it('should show a warning state if the agent version is not compatible', () => {
const user = userEvent.setup();

const host1 = hostFactory.build({ agent_version: '1.0.0' });
const host2 = hostFactory.build({ agent_version: '2.0.0' });
const state = {
...defaultInitialState,
hostsList: {
hosts: [].concat(host1, host2),
},
};

const [StatefulHostsList] = withState(<HostsList />, state);

renderWithRouter(StatefulHostsList);
const table = screen.getByRole('table');
const host1VersionCell = table.querySelector(
'tr:nth-child(1) > td:nth-child(7)'
);
expect(host1VersionCell).toHaveTextContent('1.0.0');
const icon1 = host1VersionCell.querySelector(
"[data-testid='eos-svg-component']"
);
expect(icon1.classList.toString()).toContain('fill-yellow-800');

const host2VersionCell = table.querySelector(
'tr:nth-child(2) > td:nth-child(7)'
);
expect(host2VersionCell).toHaveTextContent('2.0.0');
expect(
host2VersionCell.querySelector("[data-testid='eos-svg-component']")
).toBeNull();

user.hover(host2VersionCell);
expect(
screen.queryByText(
'Agent version 2.0.0 or greater is required for the new checks engine.'
)
).toBeInTheDocument();
});
});

describe('filtering', () => {
Expand Down
4 changes: 3 additions & 1 deletion assets/js/components/Pill/Pill.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ function Pill({
onClick = () => {},
size = 'sm',
roundedMode = 'rounded-full',
display = 'inline-flex',
}) {
return (
<span
className={classNames(
`inline-flex leading-5 font-semibold`,
`leading-5 font-semibold`,
{
'bg-green-100': !className,
'text-green-800': !className,
},
roundedMode,
sizeClasses[size],
display,
className
)}
aria-hidden="true"
Expand Down
Loading