Skip to content

Commit

Permalink
feat: [UIE-8098] - DBaaS GA Landing Page
Browse files Browse the repository at this point in the history
  • Loading branch information
mpolotsk-akamai authored and corya-akamai committed Oct 10, 2024
1 parent f51b555 commit a1e82b8
Show file tree
Hide file tree
Showing 15 changed files with 375 additions and 195 deletions.
5 changes: 5 additions & 0 deletions packages/api-v4/.changeset/pr-11039-added-1727885059968.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@linode/api-v4': Added
---

DBaaS 2.0: Add allow_list to the DatabaseInstance ([#11039](https://github.com/linode/manager/pull/11039))
1 change: 1 addition & 0 deletions packages/api-v4/src/databases/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export interface DatabaseInstance {
*/
members: Record<string, MemberType>;
platform?: string;
allow_list: string[];
}

export type ClusterSize = 1 | 2 | 3;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@linode/manager': Upcoming Features
---

Added Action Menu Column to the Databases Table and update Database Logo ([#11039](https://github.com/linode/manager/pull/11039))
1 change: 1 addition & 0 deletions packages/manager/src/factories/databases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ const adb10 = (i: number) => i % 2 === 0;

export const databaseInstanceFactory = Factory.Sync.makeFactory<DatabaseInstance>(
{
allow_list: [],
cluster_size: Factory.each((i) =>
adb10(i)
? ([1, 3][i % 2] as ClusterSize)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ import { TableCell } from 'src/components/TableCell';
import { TableRow } from 'src/components/TableRow';
import { Typography } from 'src/components/Typography';
import { useDatabaseMutation } from 'src/queries/databases/databases';
import { stringToExtendedIP } from 'src/utilities/ipUtils';

import AddAccessControlDrawer from './AddAccessControlDrawer';

import type { APIError, Database } from '@linode/api-v4';
import type { Theme } from '@mui/material/styles';
import type { ExtendedIP } from 'src/utilities/ipUtils';

const useStyles = makeStyles()((theme: Theme) => ({
addAccessControlBtn: {
Expand Down Expand Up @@ -86,11 +84,7 @@ interface Props {
}

export const AccessControls = (props: Props) => {
const {
database: { allow_list: allowList, engine, id },
description,
disabled,
} = props;
const { database, description, disabled } = props;

const { classes } = useStyles();

Expand All @@ -107,22 +101,10 @@ export const AccessControls = (props: Props) => {
setAddAccessControlDrawerOpen,
] = React.useState<boolean>(false);

const [extendedIPs, setExtendedIPs] = React.useState<ExtendedIP[]>([]);

const {
isPending: databaseUpdating,
mutateAsync: updateDatabase,
} = useDatabaseMutation(engine, id);

React.useEffect(() => {
if (allowList.length > 0) {
const allowListExtended = allowList.map(stringToExtendedIP);

setExtendedIPs(allowListExtended);
} else {
setExtendedIPs([]);
}
}, [allowList]);
} = useDatabaseMutation(database.engine, database.id);

const handleClickRemove = (accessControl: string) => {
setError(undefined);
Expand All @@ -136,7 +118,7 @@ export const AccessControls = (props: Props) => {

const handleRemoveIPAddress = () => {
updateDatabase({
allow_list: allowList.filter(
allow_list: database.allow_list.filter(
(ipAddress) => ipAddress !== accessControlToBeRemoved
),
})
Expand Down Expand Up @@ -206,7 +188,7 @@ export const AccessControls = (props: Props) => {
Manage Access Controls
</Button>
</div>
{ipTable(allowList)}
{ipTable(database.allow_list)}
<ConfirmationDialog
actions={actionsPanel}
onClose={handleDialogClose}
Expand All @@ -222,10 +204,9 @@ export const AccessControls = (props: Props) => {
</Typography>
</ConfirmationDialog>
<AddAccessControlDrawer
allowList={extendedIPs}
database={database}
onClose={() => setAddAccessControlDrawerOpen(false)}
open={addAccessControlDrawerOpen}
updateDatabase={updateDatabase}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import * as React from 'react';

import { databaseFactory } from 'src/factories';
import { IPv4List } from 'src/factories/databases';
import { stringToExtendedIP } from 'src/utilities/ipUtils';
import { mockMatchMedia, renderWithTheme } from 'src/utilities/testHelpers';

import { DatabaseInstance } from '@linode/api-v4';
import AccessControls from './AccessControls';
import AddAccessControlDrawer from './AddAccessControlDrawer';

Expand All @@ -27,13 +27,13 @@ describe('Add Access Controls drawer', () => {

it('Should open with a full list of current inbound sources that are allow listed', async () => {
const IPv4ListWithMasks = IPv4List.map((ip) => `${ip}/32`);
const db = {
id: 123,
engine: 'postgresql',
allow_list: IPv4ListWithMasks,
} as DatabaseInstance;
const { getAllByTestId } = renderWithTheme(
<AddAccessControlDrawer
allowList={IPv4ListWithMasks.map(stringToExtendedIP)}
onClose={() => null}
open={true}
updateDatabase={() => null}
/>
<AddAccessControlDrawer database={db} onClose={() => null} open={true} />
);

expect(getAllByTestId('domain-transfer-input')).toHaveLength(
Expand All @@ -46,13 +46,13 @@ describe('Add Access Controls drawer', () => {
});

it('Should have a disabled Add Inbound Sources button until an inbound source field is touched', () => {
const db = {
id: 123,
engine: 'postgresql',
allow_list: IPv4List,
} as DatabaseInstance;
const { getByText } = renderWithTheme(
<AddAccessControlDrawer
allowList={IPv4List.map(stringToExtendedIP)}
onClose={() => null}
open={true}
updateDatabase={() => null}
/>
<AddAccessControlDrawer database={db} onClose={() => null} open={true} />
);

const addAccessControlsButton = getByText('Update Access Controls').closest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import { useFormik } from 'formik';
import * as React from 'react';
import { makeStyles } from 'tss-react/mui';

import { Database, DatabaseInstance } from '@linode/api-v4';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Drawer } from 'src/components/Drawer';
import { MultipleIPInput } from 'src/components/MultipleIPInput/MultipleIPInput';
import { Notice } from 'src/components/Notice/Notice';
import { Typography } from 'src/components/Typography';
import { enforceIPMasks } from 'src/features/Firewalls/FirewallDetail/Rules/FirewallRuleDrawer.utils';
import { useDatabaseMutation } from 'src/queries/databases/databases';
import { handleAPIErrors } from 'src/utilities/formikErrorUtils';
import {
ExtendedIP,
extendedIPToString,
ipFieldPlaceholder,
stringToExtendedIP,
validateIPs,
} from 'src/utilities/ipUtils';

Expand All @@ -28,10 +31,9 @@ const useStyles = makeStyles()((theme: Theme) => ({
}));

interface Props {
allowList: ExtendedIP[];
database: Database | DatabaseInstance;
onClose: () => void;
open: boolean;
updateDatabase: any;
}

interface Values {
Expand All @@ -41,7 +43,7 @@ interface Values {
type CombinedProps = Props;

const AddAccessControlDrawer = (props: CombinedProps) => {
const { allowList, onClose, open, updateDatabase } = props;
const { database, onClose, open } = props;

const { classes } = useStyles();

Expand All @@ -58,6 +60,11 @@ const AddAccessControlDrawer = (props: CombinedProps) => {
setValues({ _allowList: _ipsWithMasks });
};

const { mutateAsync: updateDatabase } = useDatabaseMutation(
database.engine,
database.id
);

const handleUpdateAccessControlsClick = (
{ _allowList }: Values,
{
Expand Down Expand Up @@ -132,7 +139,7 @@ const AddAccessControlDrawer = (props: CombinedProps) => {
} = useFormik({
enableReinitialize: true,
initialValues: {
_allowList: allowList,
_allowList: database?.allow_list?.map(stringToExtendedIP),
},
onSubmit: handleUpdateAccessControlsClick,
validate: (values: Values) => onValidate(values),
Expand Down Expand Up @@ -182,7 +189,7 @@ const AddAccessControlDrawer = (props: CombinedProps) => {
className={classes.ipSelect}
forDatabaseAccessControls
inputProps={{ autoFocus: true }}
ips={values._allowList}
ips={values._allowList!}
onBlur={handleIPBlur}
onChange={handleIPChange}
placeholder={ipFieldPlaceholder}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,77 +1,68 @@
import { Theme, useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import * as React from 'react';
import { makeStyles } from 'tss-react/mui';
import { useHistory } from 'react-router-dom';

import { Action, ActionMenu } from 'src/components/ActionMenu/ActionMenu';
import { InlineMenuAction } from 'src/components/InlineMenuAction/InlineMenuAction';
import { ActionMenu } from 'src/components/ActionMenu/ActionMenu';

const useStyles = makeStyles()(() => ({
root: {
alignItems: 'center',
display: 'flex',
justifyContent: 'flex-end',
padding: '0px !important',
},
}));
import type { Engine } from '@linode/api-v4';
import type { Action } from 'src/components/ActionMenu/ActionMenu';

export interface ActionHandlers {
[index: string]: any;
triggerDeleteDatabase: (databaseID: number, databaseLabel: string) => void;
interface Props {
databaseEngine: Engine;
databaseId: number;
databaseLabel: string;
handlers: ActionHandlers;
}

interface Props extends ActionHandlers {
databaseID: number;
databaseLabel: string;
inlineLabel?: string;
export interface ActionHandlers {
handleDelete: () => void;
handleManageAccessControls: () => void;
handleResetPassword: () => void;
}

type CombinedProps = Props;
export const DatabaseActionMenu = (props: Props) => {
const { databaseEngine, databaseId, databaseLabel, handlers } = props;

const DatabaseActionMenu = (props: CombinedProps) => {
const { classes } = useStyles();
const theme = useTheme<Theme>();
const matchesSmDown = useMediaQuery(theme.breakpoints.down('md'));
const databaseStatus = 'running';
const isDatabaseNotRunning = databaseStatus !== 'running';

const { databaseID, databaseLabel, triggerDeleteDatabase } = props;
const history = useHistory();

const actions: Action[] = [
// TODO: add suspend action menu item once it's ready
// {
// onClick: () => {},
// title: databaseStatus === 'running' ? 'Suspend' : 'Power On',
// },
{
disabled: isDatabaseNotRunning,
onClick: handlers.handleManageAccessControls,
title: 'Manage Access Controls',
},
{
disabled: isDatabaseNotRunning,
onClick: handlers.handleResetPassword,
title: 'Reset Root Password',
},
{
disabled: isDatabaseNotRunning,
onClick: () => {
alert('Resize not yet implemented');
history.push({
pathname: `/databases/${databaseEngine}/${databaseId}/resize`,
});
},
title: 'Resize',
},
{
onClick: () => {
if (triggerDeleteDatabase !== undefined) {
triggerDeleteDatabase(databaseID, databaseLabel);
}
},
disabled: isDatabaseNotRunning,
onClick: handlers.handleDelete,
title: 'Delete',
},
];

return (
<div className={classes.root}>
{!matchesSmDown &&
actions.map((thisAction) => {
return (
<InlineMenuAction
actionText={thisAction.title}
key={thisAction.title}
onClick={thisAction.onClick}
/>
);
})}
{matchesSmDown && (
<ActionMenu
actionsList={actions}
ariaLabel={`Action menu for Database ${props.databaseLabel}`}
/>
)}
</div>
<ActionMenu
actionsList={actions}
ariaLabel={`Action menu for Database ${databaseLabel}`}
/>
);
};

export default React.memo(DatabaseActionMenu);
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,25 @@ import { useHistory } from 'react-router-dom';
import DatabaseIcon from 'src/assets/icons/entityIcons/database.svg';
import { ResourcesSection } from 'src/components/EmptyLandingPageResources/ResourcesSection';
import { getRestrictedResourceText } from 'src/features/Account/utils';
import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck';
import { sendEvent } from 'src/utilities/analytics/utils';

import {
gettingStartedGuides,
headers,
linkAnalyticsEvent,
youtubeLinkData,
} from './DatabaseLandingEmptyStateData';
} from 'src/features/Databases/DatabaseLanding/DatabaseLandingEmptyStateData';
import { useIsDatabasesEnabled } from 'src/features/Databases/utilities';
import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck';
import { sendEvent } from 'src/utilities/analytics/utils';

export const DatabaseEmptyState = () => {
const { push } = useHistory();
const { isDatabasesV2Enabled } = useIsDatabasesEnabled();
const { isDatabasesV2Enabled, isV2GAUser } = useIsDatabasesEnabled();

const isRestricted = useRestrictedGlobalGrantCheck({
globalGrantType: 'add_databases',
});

if (!isDatabasesV2Enabled) {
if (!isDatabasesV2Enabled || !isV2GAUser) {
headers.logo = '';
}

Expand Down
Loading

0 comments on commit a1e82b8

Please sign in to comment.