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

Show DevTools backend and frontend versions in UI #23399

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
15 changes: 14 additions & 1 deletion packages/react-devtools-shared/src/backend/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export default class Agent extends EventEmitter<{|
bridge.addListener('clearWarningsForFiberID', this.clearWarningsForFiberID);
bridge.addListener('copyElementPath', this.copyElementPath);
bridge.addListener('deletePath', this.deletePath);
bridge.addListener('getBackendVersion', this.getBackendVersion);
bridge.addListener('getBridgeProtocol', this.getBridgeProtocol);
bridge.addListener('getProfilingData', this.getProfilingData);
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
Expand Down Expand Up @@ -225,7 +226,12 @@ export default class Agent extends EventEmitter<{|
bridge.send('profilingStatus', true);
}

// Send the Bridge protocol after initialization in case the frontend has already requested it.
// Send the Bridge protocol and backend versions, after initialization, in case the frontend has already requested it.
// The Store may be instantiated beore the agent.
const version = process.env.DEVTOOLS_VERSION;
if (version) {
this._bridge.send('backendVersion', version);
}
this._bridge.send('bridgeProtocol', currentBridgeProtocol);

// Notify the frontend if the backend supports the Storage API (e.g. localStorage).
Expand Down Expand Up @@ -322,6 +328,13 @@ export default class Agent extends EventEmitter<{|
return null;
}

getBackendVersion = () => {
const version = process.env.DEVTOOLS_VERSION;
if (version) {
this._bridge.send('backendVersion', version);
}
};

getBridgeProtocol = () => {
this._bridge.send('bridgeProtocol', currentBridgeProtocol);
};
Expand Down
2 changes: 2 additions & 0 deletions packages/react-devtools-shared/src/bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ type SavedPreferencesParams = {|
|};

export type BackendEvents = {|
backendVersion: [string],
bridgeProtocol: [BridgeProtocol],
extensionBackendInitialized: [],
fastRefreshScheduled: [],
Expand Down Expand Up @@ -219,6 +220,7 @@ type FrontendEvents = {|
clearWarningsForFiberID: [ElementAndRendererID],
copyElementPath: [CopyElementPathParams],
deletePath: [DeletePath],
getBackendVersion: [],
getBridgeProtocol: [],
getOwnersList: [ElementAndRendererID],
getProfilingData: [{|rendererID: RendererID|}],
Expand Down
18 changes: 18 additions & 0 deletions packages/react-devtools-shared/src/devtools/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export type Capabilities = {|
* ContextProviders can subscribe to the Store for specific things they want to provide.
*/
export default class Store extends EventEmitter<{|
backendVersion: [],
collapseNodesByDefault: [],
componentFilters: [],
error: [Error],
Expand All @@ -98,6 +99,10 @@ export default class Store extends EventEmitter<{|
unsupportedBridgeProtocolDetected: [],
unsupportedRendererVersionDetected: [],
|}> {
// If the backend version is new enough to report its (NPM) version, this is it.
// This version may be displayed by the frontend for debugging purposes.
_backendVersion: string | null = null;

_bridge: FrontendBridge;

// Computed whenever _errorsAndWarnings Map changes.
Expand Down Expand Up @@ -264,6 +269,9 @@ export default class Store extends EventEmitter<{|
bridge.addListener('bridgeProtocol', this.onBridgeProtocol);
bridge.send('getBridgeProtocol');
}

bridge.addListener('backendVersion', this.onBridgeBackendVersion);
bridge.send('getBackendVersion');
}

// This is only used in tests to avoid memory leaks.
Expand Down Expand Up @@ -301,6 +309,10 @@ export default class Store extends EventEmitter<{|
}
}

get backendVersion(): string | null {
return this._backendVersion;
}

get collapseNodesByDefault(): boolean {
return this._collapseNodesByDefault;
}
Expand Down Expand Up @@ -1333,6 +1345,7 @@ export default class Store extends EventEmitter<{|
'unsupportedRendererVersion',
this.onBridgeUnsupportedRendererVersion,
);
bridge.removeListener('backendVersion', this.onBridgeBackendVersion);
bridge.removeListener('bridgeProtocol', this.onBridgeProtocol);

if (this._onBridgeProtocolTimeoutID !== null) {
Expand All @@ -1359,6 +1372,11 @@ export default class Store extends EventEmitter<{|
this.emit('unsupportedRendererVersionDetected');
};

onBridgeBackendVersion = (backendVersion: string) => {
this._backendVersion = backendVersion;
this.emit('backendVersion');
};

onBridgeProtocol = (bridgeProtocol: BridgeProtocol) => {
if (this._onBridgeProtocolTimeoutID !== null) {
clearTimeout(this._onBridgeProtocolTimeoutID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,26 @@
*/

import * as React from 'react';
import {useContext} from 'react';
import {useContext, useMemo} from 'react';
import {SettingsContext} from './SettingsContext';
import {StoreContext} from '../context';
import {CHANGE_LOG_URL} from 'react-devtools-shared/src/constants';

import styles from './SettingsShared.css';

function getChangeLogUrl(version: ?string): string | null {
if (!version) {
return null;
}

// Version numbers are in the format of: <major>.<minor>.<patch>-<sha>
// e.g. "4.23.0-f0dd459e0"
// GitHub CHANGELOG headers are in the format of: <major>.<minor>.<patch>
// but the "." are stripped from anchor tags, becomming: <major><minor><patch>
const versionAnchor = version.replace(/^(\d+)\.(\d+)\.(\d+).*/, '$1$2$3');
return `${CHANGE_LOG_URL}#${versionAnchor}`;
}

export default function GeneralSettings(_: {||}) {
const {
displayDensity,
Expand All @@ -25,7 +38,11 @@ export default function GeneralSettings(_: {||}) {
traceUpdatesEnabled,
} = useContext(SettingsContext);

const {supportsTraceUpdates} = useContext(StoreContext);
const {backendVersion, supportsTraceUpdates} = useContext(StoreContext);
const frontendVersion = process.env.DEVTOOLS_VERSION;

const showBackendVersion =
backendVersion && backendVersion !== frontendVersion;

return (
<div className={styles.Settings}>
Expand Down Expand Up @@ -70,15 +87,51 @@ export default function GeneralSettings(_: {||}) {
)}

<div className={styles.ReleaseNotes}>
{showBackendVersion && (
<div>
<ul className={styles.VersionsList}>
<li>
<Version
label="DevTools backend version:"
version={backendVersion}
/>
</li>
<li>
<Version
label="DevTools frontend version:"
version={frontendVersion}
/>
</li>
</ul>
</div>
)}
{!showBackendVersion && (
<Version label="DevTools version:" version={frontendVersion} />
)}
</div>
</div>
);
}

function Version({label, version}: {|label: string, version: ?string|}) {
const changelogLink = useMemo(() => {
return getChangeLogUrl(version);
}, [version]);

if (version == null) {
return null;
} else {
return (
<>
{label}{' '}
<a
className={styles.ReleaseNotesLink}
target="_blank"
rel="noopener noreferrer"
href={CHANGE_LOG_URL}>
View release notes
</a>{' '}
for DevTools version {process.env.DEVTOOLS_VERSION}
</div>
</div>
);
href={changelogLink}>
{version}
</a>
</>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,10 @@

.Warning {
color: var(--color-error-text);
}

.VersionsList {
list-style: none;
padding: 0;
margin: 0;
}
2 changes: 0 additions & 2 deletions packages/react-devtools-shell/src/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import Iframe from './Iframe';
import EditableProps from './EditableProps';
import ElementTypes from './ElementTypes';
import Hydration from './Hydration';
import InlineWarnings from './InlineWarnings';
import InspectableElements from './InspectableElements';
import ReactNativeWeb from './ReactNativeWeb';
import ToDoList from './ToDoList';
Expand Down Expand Up @@ -83,7 +82,6 @@ function mountTestApp() {
mountApp(Hydration);
mountApp(ElementTypes);
mountApp(EditableProps);
mountApp(InlineWarnings);
mountApp(ReactNativeWeb);
mountApp(Toggle);
mountApp(ErrorBoundaries);
Expand Down
Loading