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

feat: [UIE-8098] - DBaaS 2.0 Landing Page GA #11039

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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