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

PMM-11148 Service management - Add/Edit service #599

Merged
merged 56 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
0b4b925
PMM-1148 initial inventory management rework
matejkubinec Jan 20, 2023
fa2071a
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jan 20, 2023
c645b96
PMM-11148 Refactor AddInstance
matejkubinec Jan 20, 2023
b830536
PMM-11148 remove unnecessary tooltip icon
matejkubinec Jan 20, 2023
89f5dad
PMM-11148 Fix discovery tests
matejkubinec Jan 20, 2023
b5abf95
PMM-11148 Fix add instance submission
matejkubinec Jan 20, 2023
8d38808
PMM-11148 Integrate service editing api
matejkubinec Jan 23, 2023
220a29c
PMM-11148 fix custom labels field
matejkubinec Jan 23, 2023
f617393
PMM-11148 add success modal
matejkubinec Jan 23, 2023
067069f
PMM-11148 fix inventory test
matejkubinec Jan 23, 2023
807e0ee
PMM-11148 add labels tooltip & fix description
matejkubinec Jan 24, 2023
dab3e4a
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jan 24, 2023
9c89f74
PMM-11148 refactor
matejkubinec Jan 24, 2023
4427d62
PMM-11148 add required property
matejkubinec Jan 24, 2023
51e6ab2
PMM-11148 add feature check
matejkubinec Jan 25, 2023
5bd1a27
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jan 25, 2023
7dba363
PMM-11148 adjust services delete
matejkubinec Jan 25, 2023
d8d2ca4
PMM-11148 remove documentation links
matejkubinec Jan 25, 2023
32aec8a
PMM-11148 Wording, boolean state, etc.
matejkubinec Jan 27, 2023
f62ac96
PMM-11148 update tablestat options name
matejkubinec Jan 27, 2023
874e2a8
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jan 30, 2023
de67228
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Feb 2, 2023
d308f27
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Feb 6, 2023
357f8e2
PMM-11148 Extract messages for service tab
matejkubinec Feb 6, 2023
7537dc1
Merge branch main of github.com:percona-platform/grafana into PMM-111…
matejkubinec Feb 8, 2023
8709551
PMM-11148 Add warning when changing cluster label
matejkubinec Feb 10, 2023
55eb6b4
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Feb 14, 2023
71f196e
PMM-11148 Add docs link to confirm edit service dialog
matejkubinec Feb 16, 2023
b1bf487
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Feb 16, 2023
9d8525e
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Feb 21, 2023
43473b8
PMM-11148 Update api endpoint name
matejkubinec Feb 22, 2023
4c4f9f5
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Mar 7, 2023
15a3c83
Merge branch main of github.com:percona-platform/grafana into PMM-111…
matejkubinec Apr 14, 2023
9fe8f88
PMM-11148 Move delete services modal to component
matejkubinec Apr 17, 2023
5fe4bb2
PMM-11148 Fix bugs
matejkubinec Apr 18, 2023
78e17bf
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Apr 18, 2023
a13c239
PMM-11148 Call onSucces when single service is deleted
matejkubinec Apr 18, 2023
101f1e4
PMM-11148 Import FC directly
matejkubinec Apr 18, 2023
092966d
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jun 12, 2023
f3384e1
PMM-11148 Fix spacing
matejkubinec Jun 12, 2023
28586dd
PMM-11148 Fix errors from component library integration
matejkubinec Jun 12, 2023
fe5c9f9
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jun 12, 2023
b621a2b
PMM-11148 Fix DeleteServicesModal tests
matejkubinec Jun 13, 2023
423185f
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jun 19, 2023
6347f51
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jul 6, 2023
fdcb0fc
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jul 10, 2023
17a2e79
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jul 14, 2023
97257a9
PMM-11148 Use correct urls for services
matejkubinec Jul 19, 2023
ba66b46
Merge branch 'PMM-11148-inventory-manage-labels' of github.com:percon…
matejkubinec Jul 19, 2023
e96a684
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Jul 19, 2023
ba3674a
PMM-11148 Use shortened docs link
matejkubinec Aug 2, 2023
6637264
PMM-11148 Increase width for azure instance form
matejkubinec Aug 16, 2023
e7488c4
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Aug 16, 2023
3511995
PMM-11148 Jump straight to add instance page & reorder
matejkubinec Aug 22, 2023
f3a73bf
Merge branch 'main' of github.com:percona-platform/grafana into PMM-1…
matejkubinec Aug 22, 2023
1b66f01
Merge branch 'main' into PMM-11148-inventory-manage-labels
matejkubinec Aug 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const Messages = {
sectionTitle: 'Select service type',
description: 'Select the service type you want to configure and then add it to your inventory.',
titles: {
rds: 'Amazon RDS',
azure: 'Microsoft Azure MySQL or PostgreSQL',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,30 @@
import { css } from '@emotion/css';

import { GrafanaTheme } from '@grafana/data';
import { GrafanaTheme2 } from '@grafana/data';

export const getStyles = ({ border, colors, spacing, typography }: GrafanaTheme) => ({
navigationButton: css`
export const getStyles = (theme: GrafanaTheme2) => ({
Content: css`
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
padding-bottom: 1.2em;
margin: ${spacing.sm};
border-radius: ${border.radius.md};
width: 230px;
height: 160px;
text-align: center;
background-color: transparent;
border: ${border.width.sm} dashed ${colors.border2};
:hover {
cursor: pointer;
background-color: ${colors.dropdownOptionHoverBg};
border: ${border.width.sm} solid ${colors.border2};
}
align-items: flex-start;
`,
navigationPanel: css`
NavigationPanel: css`
display: flex;
flex-direction: row;
justify-content: center;
justify-content: flex-start;
flex-wrap: wrap;
max-width: 800px;
max-width: 825px;
width: 100%;
overflow: hidden;
gap: ${theme.spacing(2)};
padding: 3px;
margin: -3px;
`,
content: css`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 2em;
InstanceCard: css`
width: 375px;
margin: 0;
`,
addInstance: css`
margin-top: ${spacing.sm};
font-size: ${typography.size.sm};
`,
addInstanceTitle: css`
margin-top: ${spacing.sm};
overflow: hidden;
line-height: ${typography.lineHeight.md};
width: 65%;
height: 3em;
Description: css`
color: ${theme.colors.text.secondary};
`,
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import { Provider } from 'react-redux';
import { configureStore } from 'app/store/configureStore';
import { StoreState } from 'app/types';

import { InstanceAvailable } from '../../panel.types';

import { AddInstance } from './AddInstance';
import { instanceList } from './AddInstance.constants';

jest.mock('app/percona/settings/Settings.service');

const selectedInstanceType: InstanceAvailable = { type: '' };

describe('AddInstance page::', () => {
it('should render a given number of links', async () => {
const ui = withStore(<AddInstance showAzure={false} onSelectInstanceType={() => {}} />);
const ui = withStore(
<AddInstance showAzure={false} onSelectInstanceType={() => {}} selectedInstanceType={selectedInstanceType} />
);
await waitFor(() => render(ui));

expect(screen.getAllByRole('button')).toHaveLength(instanceList.length);
Expand All @@ -22,7 +28,9 @@ describe('AddInstance page::', () => {
});

it('should render azure option', async () => {
const ui = withStore(<AddInstance showAzure onSelectInstanceType={() => {}} />);
const ui = withStore(
<AddInstance showAzure onSelectInstanceType={() => {}} selectedInstanceType={selectedInstanceType} />
);
await waitFor(() => render(ui));

expect(screen.getAllByRole('button')).toHaveLength(instanceList.length + 1);
Expand All @@ -35,13 +43,15 @@ describe('AddInstance page::', () => {
it('should invoke a callback with a proper instance type', async () => {
const onSelectInstanceType = jest.fn();

const ui = withStore(<AddInstance showAzure onSelectInstanceType={onSelectInstanceType} />);
const ui = withStore(
<AddInstance showAzure onSelectInstanceType={onSelectInstanceType} selectedInstanceType={selectedInstanceType} />
);
render(ui);

expect(onSelectInstanceType).toBeCalledTimes(0);

const button = await screen.findByTestId('rds-instance');
fireEvent.click(button);
const button = (await screen.findByTestId('rds-instance')).querySelector('button');
fireEvent.click(button!);

expect(onSelectInstanceType).toBeCalledTimes(1);
expect(onSelectInstanceType.mock.calls[0][0]).toStrictEqual({ type: 'rds' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
import React, { FC, useMemo } from 'react';
import { v4 as uuidv4 } from 'uuid';

import { useStyles } from '@grafana/ui';
import { Database } from 'app/percona/shared/components/Elements/Icons/Database';
import { Card, Icon, useStyles2 } from '@grafana/ui';
import { Databases } from 'app/percona/shared/core';
import * as UserFlow from 'app/percona/shared/core/reducers/userFlow';
import { useDispatch } from 'app/types';
Expand All @@ -12,37 +11,34 @@ import { InstanceAvailableType, InstanceTypesExtra } from '../../panel.types';

import { Messages } from './AddInstance.messages';
import { getStyles } from './AddInstance.styles';
import { AddInstanceProps, SelectInstanceProps } from './AddInstance.types';
import { AddInstanceProps, InstanceListItem, SelectInstanceProps } from './AddInstance.types';

export const SelectInstance: FC<SelectInstanceProps> = ({ type, selectInstanceType, title }) => {
const styles = useStyles(getStyles);
export const SelectInstance: FC<SelectInstanceProps> = ({ type, isSelected, icon, selectInstanceType, title }) => {
const styles = useStyles2(getStyles);

return (
<button
className={styles.navigationButton}
data-testid={`${type}-instance`}
onClick={selectInstanceType(type)}
type="button"
>
<Database />
<span className={styles.addInstanceTitle}>{title}</span>
<span className={styles.addInstance}>{Messages.titles.addInstance}</span>
</button>
<Card data-testid={`${type}-instance`} onClick={selectInstanceType(type)} className={styles.InstanceCard}>
<Card.Heading>{title}</Card.Heading>
<Card.Description>{Messages.titles.addInstance}</Card.Description>
<Card.Figure>
<Icon size="xxxl" name={icon ? icon : 'database'} />
</Card.Figure>
</Card>
);
};

export const AddInstance: FC<AddInstanceProps> = ({ onSelectInstanceType, showAzure }) => {
const styles = useStyles(getStyles);
const instanceList = useMemo(
export const AddInstance: FC<AddInstanceProps> = ({ selectedInstanceType, onSelectInstanceType, showAzure }) => {
const styles2 = useStyles2(getStyles);
const instanceList = useMemo<InstanceListItem[]>(
() => [
{ type: InstanceTypesExtra.rds, title: Messages.titles.rds },
{ type: InstanceTypesExtra.azure, title: Messages.titles.azure, isHidden: !showAzure },
{ type: Databases.postgresql, title: Messages.titles.postgresql },
{ type: Databases.mysql, title: Messages.titles.mysql },
{ type: Databases.mongodb, title: Messages.titles.mongodb },
{ type: Databases.proxysql, title: Messages.titles.proxysql },
{ type: Databases.mysql, title: Messages.titles.mysql, icon: 'percona-database-mysql' },
{ type: Databases.mongodb, title: Messages.titles.mongodb, icon: 'percona-database-mongodb' },
{ type: Databases.postgresql, title: Messages.titles.postgresql, icon: 'percona-database-postgresql' },
{ type: Databases.proxysql, title: Messages.titles.proxysql, icon: 'percona-database-proxysql' },
{ type: Databases.haproxy, title: Messages.titles.haproxy, icon: 'percona-database-haproxy' },
{ type: InstanceTypesExtra.external, title: Messages.titles.external },
{ type: Databases.haproxy, title: Messages.titles.haproxy },
{ type: InstanceTypesExtra.azure, title: Messages.titles.azure, isHidden: !showAzure },
],
[showAzure]
);
Expand All @@ -61,14 +57,18 @@ export const AddInstance: FC<AddInstanceProps> = ({ onSelectInstanceType, showAz
};

return (
<section className={styles.content}>
<nav className={styles.navigationPanel}>
<section className={styles2.Content}>
<h2>{Messages.sectionTitle}</h2>
<p className={styles2.Description}>{Messages.description}</p>
<nav className={styles2.NavigationPanel}>
{instanceList
.filter(({ isHidden }) => !isHidden)
.map((item) => (
<SelectInstance
isSelected={item.type === selectedInstanceType.type}
selectInstanceType={selectInstanceType}
type={item.type}
icon={item.icon}
title={item.title}
key={item.type}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { InstanceAvailable } from '../../panel.types';
import { IconName } from '@grafana/data';

export interface SelectInstanceProps {
type: string;
title: string;
selectInstanceType: (type: string) => () => void;
import { InstanceAvailable, InstanceAvailableType } from '../../panel.types';

export interface SelectInstanceProps extends InstanceListItem {
isSelected: boolean;
selectInstanceType: (type: InstanceAvailableType) => () => void;
}

export interface AddInstanceProps {
selectedInstanceType: InstanceAvailable;
onSelectInstanceType: (arg: InstanceAvailable) => void;
showAzure: boolean;
}

export interface InstanceListItem {
type: InstanceAvailableType;
icon?: IconName;
title: string;
isHidden?: boolean;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
export const Messages = {
pageTitleSelection: 'Inventory / Add service / Step 1 of 2',
pageTitleConfiguration: 'Inventory / Add service / Step 2 of 2',
selectionStep: {
cancel: 'Cancel',
next: 'Next step: Configuration',
},
configurationStep: {
cancel: 'Cancel',
next: 'Add service',
discover: 'Discover',
},
form: {
trackingOptions: {
none: "Don't track",
Expand All @@ -22,4 +33,8 @@ export const Messages = {
addRemoteInstance: 'Add remote instance',
},
},
success: {
title: (service: string) => `Service “${service}” added to your inventory`,
description: (serviceType: string) => `Your ${serviceType} service instance is now ready to be monitored.`,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const getStyles = ({ spacing }: GrafanaTheme) => ({
white-space: nowrap;
`,
addRemoteInstanceTitle: css`
text-align: center;
text-align: left;
`,
addRemoteInstanceButtons: css`
margin-top: ${spacing.md};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jest.mock('app/percona/shared/helpers/logger', () => {
describe('Add remote instance:: ', () => {
it('should render correct for mysql and postgres and highlight empty mandatory fields on submit', async () => {
const type = Databases.mysql;
render(<AddRemoteInstance instance={{ type, credentials: {} }} selectInstance={jest.fn()} />);
render(<AddRemoteInstance onSubmit={jest.fn()} instance={{ type, credentials: {} }} selectInstance={jest.fn()} />);

expect(screen.getByTestId('address-text-input').classList.contains('invalid')).toBe(false);
expect(screen.getByTestId('username-text-input').classList.contains('invalid')).toBe(false);
Expand All @@ -35,7 +35,7 @@ describe('Add remote instance:: ', () => {

it('should render for external service and highlight empty mandatory fields on submit', async () => {
const type = InstanceTypesExtra.external;
render(<AddRemoteInstance instance={{ type, credentials: {} }} selectInstance={jest.fn()} />);
render(<AddRemoteInstance onSubmit={jest.fn()} instance={{ type, credentials: {} }} selectInstance={jest.fn()} />);

expect(screen.getByTestId('address-text-input').classList.contains('invalid')).toBe(false);
expect(screen.getByTestId('metrics_path-text-input').classList.contains('invalid')).toBe(false);
Expand All @@ -55,7 +55,7 @@ describe('Add remote instance:: ', () => {
it('should render correct for HAProxy and highlight empty mandatory fields on submit', async () => {
const type = Databases.haproxy;

render(<AddRemoteInstance instance={{ type, credentials: {} }} selectInstance={jest.fn()} />);
render(<AddRemoteInstance onSubmit={jest.fn()} instance={{ type, credentials: {} }} selectInstance={jest.fn()} />);

expect(screen.getByTestId('address-text-input').classList.contains('invalid')).toBe(false);
expect(screen.getByTestId('username-text-input').classList.contains('invalid')).toBe(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import { FormApi } from 'final-form';
import React, { FC, useCallback, useMemo, useState } from 'react';
import { Form as FormFinal } from 'react-final-form';

import { Button, useStyles } from '@grafana/ui';
import { AppEvents } from '@grafana/data';
import { useStyles } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { useCancelToken } from 'app/percona/shared/components/hooks/cancelToken.hook';
import { Databases } from 'app/percona/shared/core';
import { isApiCancelError } from 'app/percona/shared/helpers/api';
import { logger } from 'app/percona/shared/helpers/logger';

import { InstanceAvailableType, InstanceTypes, InstanceTypesExtra, INSTANCE_TYPES_LABELS } from '../../panel.types';
import { ADD_INSTANCE_FORM_NAME } from '../../panel.constants';
import { InstanceTypesExtra, InstanceTypes, INSTANCE_TYPES_LABELS, InstanceAvailableType } from '../../panel.types';

import { ADD_AZURE_CANCEL_TOKEN, ADD_RDS_CANCEL_TOKEN } from './AddRemoteInstance.constants';
import { Messages } from './AddRemoteInstance.messages';
Expand All @@ -35,7 +38,10 @@ import {
import { ExternalServiceConnectionDetails } from './FormParts/ExternalServiceConnectionDetails/ExternalServiceConnectionDetails';
import { HAProxyConnectionDetails } from './FormParts/HAProxyConnectionDetails/HAProxyConnectionDetails';

const AddRemoteInstance: FC<AddRemoteInstanceProps> = ({ instance: { type, credentials }, selectInstance }) => {
const AddRemoteInstance: FC<AddRemoteInstanceProps> = ({
instance: { type, credentials },
onSubmit: submitWrapper,
}) => {
const styles = useStyles(getStyles);

const { remoteInstanceCredentials, discoverName } = getInstanceData(type, credentials);
Expand Down Expand Up @@ -70,7 +76,10 @@ const AddRemoteInstance: FC<AddRemoteInstanceProps> = ({ instance: { type, crede
} else {
await AddRemoteInstanceService.addRemote(type, values, generateToken(remoteToken(type)));
}

appEvents.emit(AppEvents.alertSuccess, [
Messages.success.title(values.serviceName || values.address || ''),
Messages.success.description(INSTANCE_TYPES_LABELS[type as Databases]),
]);
window.location.href = '/graph/inventory/';
} catch (e) {
if (isApiCancelError(e)) {
Expand Down Expand Up @@ -130,37 +139,23 @@ const AddRemoteInstance: FC<AddRemoteInstanceProps> = ({ instance: { type, crede
if (databaseType === '') {
return Messages.form.titles.addRemoteInstance;
}
return `Add remote ${INSTANCE_TYPES_LABELS[databaseType]} Instance`;
return `Configuring ${INSTANCE_TYPES_LABELS[databaseType]} service`;
};

return (
<div className={styles.formWrapper}>
<FormFinal
onSubmit={onSubmit}
onSubmit={(values) => submitWrapper(onSubmit(values))}
initialValues={initialValues}
mutators={{
setValue: ([field, value], state, { changeValue }) => {
changeValue(state, field, () => value);
},
}}
render={({ form, handleSubmit }) => (
<form onSubmit={handleSubmit} data-testid="add-remote-instance-form">
<h4 className={styles.addRemoteInstanceTitle}>{getHeader(type)}</h4>
<form id={ADD_INSTANCE_FORM_NAME} onSubmit={handleSubmit} data-testid="add-remote-instance-form">
<h3 className={styles.addRemoteInstanceTitle}>{getHeader(type)}</h3>
{formParts(form)}
<div className={styles.addRemoteInstanceButtons}>
<Button id="addInstance" disabled={loading} type="submit">
{Messages.form.buttons.addService}
</Button>
<Button
variant="secondary"
onClick={() => selectInstance({ type: '' })}
disabled={loading}
className={styles.returnButton}
icon="arrow-left"
>
{Messages.form.buttons.toMenu}
</Button>
</div>
</form>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface InstanceData {
export interface AddRemoteInstanceProps {
instance: InstanceAvailable;
selectInstance: SelectInstance;
onSubmit: (submitPromise: Promise<void>) => void;
}

export enum DefaultPorts {
Expand Down
Loading
Loading