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

change: [M3-7785] - Improve Dev Tools #10220

Merged
merged 10 commits into from
Feb 26, 2024
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
18 changes: 16 additions & 2 deletions docs/development-guide/10-local-dev-tools.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
# Local Dev Tools

To facilitate development and debugging, Cloud Manager includes a "Dev Tools" mode. Currently this mode is used for feature flag toggling, data mocking, and environment switching.
To facilitate development and debugging, Cloud Manager includes a "Dev Tools" mode. Currently this mode is used for feature flag toggling, data mocking, and theme & environment switching.

In order to access the dev tools, hover or click (mobile) on the πŸ›  icon in the lower left corner of your browser window. The icon will be colored green if MSW is enabled.

This mode is enabled by default while running the development server. To disable it, add `?dev-tools=false` to the URL, or write `dev-tools: false` to local storage.

This mode is disabled by default in production builds, but can be enabled by adding `?dev-tools=true` to the URL, or `dev-tools: true` to local storage.
This mode is disabled by default in production builds.

## Feature Flags

The display of the Flags in dev tools is defined in the `options` array in `FeatureFlagTool.tsx`. While it is convenient to add those switches to the dev tools, it is not always necessary as they can clutter the UI. Additionally, it is important to clean them up once the feature has been battle tested in production.

The flags on/off values are stored in local storage for convenience and will be remembered on reload or app restart.

By default, the boolean flags checkboxes represent their true values as returned by Launch Darkly (dev environment). Hitting the reset button will bring them back to those default values and clear local storage.

## Theme Select

The theme select in dev tools is a convenient way to store the theme choice while MSW is enabled (it won't affect your actual Application settings/preferences). The default is "system".

## Writing a new tool

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Changed
---

Improve dev tools UI ([#10220](https://github.com/linode/manager/pull/10220))
14 changes: 13 additions & 1 deletion packages/manager/src/dev-tools/FeatureFlagTool.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Grid from '@mui/material/Unstable_Grid2';
import { useFlags as ldUseFlags } from 'launchdarkly-react-client-sdk';
import * as React from 'react';
import { useDispatch } from 'react-redux';

Expand All @@ -8,7 +9,6 @@ import { Dispatch } from 'src/hooks/types';
import { useFlags } from 'src/hooks/useFlags';
import { setMockFeatureFlags } from 'src/store/mockFeatureFlags';
import { getStorage, setStorage } from 'src/utilities/storage';

const MOCK_FEATURE_FLAGS_STORAGE_KEY = 'devTools/mock-feature-flags';

const options: { flag: keyof Flags; label: string }[] = [
Expand All @@ -28,6 +28,7 @@ const options: { flag: keyof Flags; label: string }[] = [
export const FeatureFlagTool = withFeatureFlagProvider(() => {
const dispatch: Dispatch = useDispatch();
const flags = useFlags();
const ldFlags = ldUseFlags();

React.useEffect(() => {
const storedFlags = getStorage(MOCK_FEATURE_FLAGS_STORAGE_KEY);
Expand All @@ -48,6 +49,14 @@ export const FeatureFlagTool = withFeatureFlagProvider(() => {
setStorage(MOCK_FEATURE_FLAGS_STORAGE_KEY, updatedFlags);
};

/**
* This will reset the flags values to the Launch Darkly defaults (as returned from the LD dev environment)
*/
const resetFlags = () => {
dispatch(setMockFeatureFlags(ldFlags));
setStorage(MOCK_FEATURE_FLAGS_STORAGE_KEY, '');
};

return (
<Grid container>
<Grid xs={12}>
Expand Down Expand Up @@ -75,6 +84,9 @@ export const FeatureFlagTool = withFeatureFlagProvider(() => {
</div>
);
})}
<button onClick={resetFlags} style={{ marginTop: 8 }}>
Reset default flags
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reword this to Reset to LaunchDarkly default flags for more clarity?

Suggested change
Reset default flags
Reset to LaunchDarkly default flags

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I almost made this same comment, then deleted it, and asked for a code comment, ha. I do still think it would be nice.

I was thinking of (1) more text requiring further height increase of the dev tools panel and (2) whether we'd want to specify LD if to save some trouble of updating it if we were to switch to another tool in the future - but really that's such an easy change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same brain cell 🧠 . (1) We can shorthand to LD to save some width space (2) Agreed

</button>
</div>
</Grid>
</Grid>
Expand Down
25 changes: 25 additions & 0 deletions packages/manager/src/dev-tools/Preferences.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import LinkIcon from '@mui/icons-material/Link';
import * as React from 'react';

export const Preferences = () => {
return (
<>
<h4 style={{ marginBottom: 4 }}>Preferences</h4>
<a
href="/profile/settings?preferenceEditor=true"
style={{ color: '#fff' }}
>
Open preference Modal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love this. ++

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is from @jaalah-akamai!

<LinkIcon
style={{
height: 20,
left: 4,
position: 'relative',
top: 6,
width: 20,
}}
/>
</a>
</>
);
};
4 changes: 3 additions & 1 deletion packages/manager/src/dev-tools/ServiceWorkerTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export const ServiceWorkerTool = () => {
<>
<span style={{ marginRight: 8 }}>
<span style={{ marginRight: 8 }}>Mock Service Worker:</span>
{isMSWEnabled ? 'Enabled' : 'Disabled'}
<span style={{ color: isMSWEnabled ? '#aaff00' : 'white' }}>
{isMSWEnabled ? 'Enabled' : 'Disabled'}
</span>
</span>
<input
checked={isMSWEnabled}
Expand Down
45 changes: 45 additions & 0 deletions packages/manager/src/dev-tools/ThemeSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';

import { getStorage, setStorage } from 'src/utilities/storage';

export const MOCK_THEME_STORAGE_KEY = 'devTools/theme';

import type { ThemeChoice } from 'src/utilities/theme';

export const ThemeSelector = () => {
const [mockTheme, setMockTheme] = React.useState<ThemeChoice>('system');

React.useEffect(() => {
const storedTheme = getStorage(MOCK_THEME_STORAGE_KEY);
if (storedTheme) {
setMockTheme(storedTheme);
}
}, []);

const handleSetTheme = (e: React.ChangeEvent<HTMLSelectElement>) => {
const selectedValue = e.target.value as ThemeChoice;

setMockTheme(selectedValue);
setStorage(MOCK_THEME_STORAGE_KEY, selectedValue);
window.location.reload();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching theme is not going to be as common as toggling or flags. I opted to no add this to redux as there is not a lot of value there and it would increase the complexity of this feature by a lot.

};

return (
<Grid container>
<Grid xs={12}>
<h4 style={{ marginBottom: 0 }}>MSW Theme Selector</h4>
<p style={{ marginBottom: 8, marginTop: 0 }}>
(only when MSW is enabled)
</p>
</Grid>
<Grid xs={12}>
<select onChange={handleSetTheme} value={mockTheme}>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</Grid>
</Grid>
);
};
12 changes: 10 additions & 2 deletions packages/manager/src/dev-tools/dev-tools.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@
width: 60px;
transition: all 0.3s;
z-index: 1;
overflow: auto;
}

#dev-tools.mswEnabled svg {
color: #aaff00;
}

#dev-tools:hover {
height: 325px;
height: 375px;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

increasing the height here a bit to allow for more room and the new reset button

width: 100%;
opacity: 0.9;
overflow: auto;
}

#dev-tools:hover svg {
color: white;
}

#dev-tools .tools {
Expand Down
12 changes: 10 additions & 2 deletions packages/manager/src/dev-tools/dev-tools.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Handyman from '@mui/icons-material/Handyman';
import Grid from '@mui/material/Unstable_Grid2';
import React from 'react';
import { createRoot } from 'react-dom/client';
Expand All @@ -10,12 +11,17 @@ import './dev-tools.css';
import { EnvironmentToggleTool } from './EnvironmentToggleTool';
import { FeatureFlagTool } from './FeatureFlagTool';
import { MockDataTool } from './MockDataTool';
import { Preferences } from './Preferences';
import { isMSWEnabled } from './ServiceWorkerTool';
import { ThemeSelector } from './ThemeSelector';

function install(store: ApplicationStore) {
function DevTools() {
return (
<div id="dev-tools">
<div>πŸ› </div>
<div className={isMSWEnabled ? 'mswEnabled' : ''} id="dev-tools">
<div>
<Handyman />
</div>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this way we can color it with CSS

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also/or add text to indicate the MSW is "on"? For accessibility, color should not be the only indicator haha

<Grid className="tools" container spacing={2}>
<Grid sm={2} xs={4}>
<FeatureFlagTool />
Expand All @@ -28,6 +34,8 @@ function install(store: ApplicationStore) {
{!isProductionBuild || ENABLE_DEV_TOOLS ? (
<Grid md={3} sm={5} xs={4}>
<MockDataTool />
<ThemeSelector />
<Preferences />
</Grid>
) : null}
</Grid>
Expand Down
36 changes: 21 additions & 15 deletions packages/manager/src/mocks/serverHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import { DateTime } from 'luxon';
import { rest } from 'msw';

import { regions } from 'src/__data__/regionsData';
import { MOCK_THEME_STORAGE_KEY } from 'src/dev-tools/ThemeSelector';
import {
VLANFactory,
abuseTicketNotificationFactory,
// abuseTicketNotificationFactory,
accountAvailabilityFactory,
accountBetaFactory,
accountFactory,
Expand Down Expand Up @@ -103,6 +104,7 @@ import { accountLoginFactory } from 'src/factories/accountLogin';
import { accountUserFactory } from 'src/factories/accountUsers';
import { grantFactory, grantsFactory } from 'src/factories/grants';
import { pickRandom } from 'src/utilities/random';
import { getStorage } from 'src/utilities/storage';

export const makeResourcePage = <T>(
e: T[],
Expand Down Expand Up @@ -1146,7 +1148,11 @@ export const handlers = [
return res(ctx.json(makeResourcePage(vlans)));
}),
rest.get('*/profile/preferences', (req, res, ctx) => {
return res(ctx.json({}));
return res(
ctx.json({
theme: getStorage(MOCK_THEME_STORAGE_KEY) ?? 'system',
})
);
}),
rest.get('*/profile/devices', (req, res, ctx) => {
return res(ctx.json(makeResourcePage([])));
Expand Down Expand Up @@ -1819,18 +1825,18 @@ export const handlers = [
when: null,
};

const emailBounce = notificationFactory.build({
body: null,
entity: null,
label: 'We are unable to send emails to your billing email address!',
message: 'We are unable to send emails to your billing email address!',
severity: 'major',
type: 'billing_email_bounce',
until: null,
when: null,
});
// const emailBounce = notificationFactory.build({
// body: null,
// entity: null,
// label: 'We are unable to send emails to your billing email address!',
// message: 'We are unable to send emails to your billing email address!',
// severity: 'major',
// type: 'billing_email_bounce',
// until: null,
// when: null,
// });

const abuseTicket = abuseTicketNotificationFactory.build();
// const abuseTicket = abuseTicketNotificationFactory.build();

const migrationNotification = notificationFactory.build({
entity: { id: 0, label: 'linode-0', type: 'linode' },
Expand Down Expand Up @@ -1926,8 +1932,8 @@ export const handlers = [
outageNotification,
minorSeverityNotification,
criticalSeverityNotification,
abuseTicket,
emailBounce,
// abuseTicket,
// emailBounce,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those ALWAYS show up, and on EVERY page. They can now be toggled on manually as needed

Screenshot 2024-02-22 at 11 58 58

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that these are annoying. I don't think I'll miss them, but not sure if anyone else on the team has a reason behind why they have historically been kept visible by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels to me like something that was implemented at once and never changed to no particular reason. If anyone is affected I will add them back but I doubt it

migrationNotification,
balanceNotification,
blockStorageMigrationScheduledNotification,
Expand Down
Loading