[APM] Service map download in debug mode (#69350)

* [APM] Service map download in debug mode

Add a download button when debug mode is enabled that downloads JSON of
the map.

Add an upload button to the Storybook.
This commit is contained in:
Nathan L Smith 2020-06-18 10:53:27 -05:00 committed by GitHub
parent 338ae00384
commit af5874d9d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 140 additions and 28 deletions

View file

@ -56,18 +56,64 @@ function doZoom(cy: cytoscape.Core | undefined, increment: number) {
}
}
function useDebugDownloadUrl(cy?: cytoscape.Core) {
const [downloadUrl, setDownloadUrl] = useState<string | undefined>(undefined);
const debug = sessionStorage.getItem('apm_debug') === 'true';
// Handle elements changes to update the download URL
useEffect(() => {
const elementsHandler: cytoscape.EventHandler = (event) => {
// @ts-ignore The `true` argument to `cy.json` is to flatten the elements
// (instead of having them broken into nodes/edges.) DefinitelyTyped has
// this wrong.
const elementsJson = event.cy.json(true)?.elements.map((element) => ({
data: element.data,
}));
setDownloadUrl(
elementsJson.length > 0 && debug
? `data:application/json;charset=utf-8,${encodeURIComponent(
JSON.stringify({ elements: elementsJson }, null, ' ')
)}`
: undefined
);
};
if (cy) {
cy.on('add remove', elementsHandler);
}
return () => {
if (cy) {
cy.off('add remove', undefined, elementsHandler);
}
};
}, [cy, debug]);
return downloadUrl;
}
export function Controls() {
const cy = useContext(CytoscapeContext);
const { urlParams } = useUrlParams();
const currentSearch = urlParams.kuery ?? '';
const [zoom, setZoom] = useState((cy && cy.zoom()) || 1);
const downloadUrl = useDebugDownloadUrl(cy);
// Handle zoom events
useEffect(() => {
const zoomHandler: cytoscape.EventHandler = (event) => {
setZoom(event.cy.zoom());
};
if (cy) {
cy.on('zoom', (event) => {
setZoom(event.cy.zoom());
});
cy.on('zoom', zoomHandler);
}
return () => {
if (cy) {
cy.off('zoom', undefined, zoomHandler);
}
};
}, [cy]);
function center() {
@ -102,6 +148,9 @@ export function Controls() {
const centerLabel = i18n.translate('xpack.apm.serviceMap.center', {
defaultMessage: 'Center',
});
const downloadLabel = i18n.translate('xpack.apm.serviceMap.download', {
defaultMessage: 'Download',
});
const viewFullMapLabel = i18n.translate('xpack.apm.serviceMap.viewFullMap', {
defaultMessage: 'View full service map',
});
@ -165,6 +214,22 @@ export function Controls() {
</EuiToolTip>
</Panel>
)}
{downloadUrl && (
<Panel hasShadow={true} paddingSize="none">
<EuiToolTip
anchorClassName="eui-displayInline"
content={downloadLabel}
>
<Button
aria-label={downloadLabel}
color="text"
download="service-map.json"
href={downloadUrl}
iconType="download"
/>
</EuiToolTip>
</Panel>
)}
</ControlsContainer>
);
}

View file

@ -9,9 +9,12 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiButton,
EuiForm,
EuiFieldNumber,
EuiToolTip,
EuiCodeEditor,
EuiSpacer,
EuiFilePicker,
} from '@elastic/eui';
import { storiesOf } from '@storybook/react';
import React, { useState, useEffect } from 'react';
@ -99,12 +102,14 @@ storiesOf(STORYBOOK_PATH, module).add(
const [json, setJson] = useState<string>(
getSessionJson() || JSON.stringify(exampleResponseTodo, null, 2)
);
const [error, setError] = useState<string | undefined>();
const [elements, setElements] = useState<any[]>([]);
useEffect(() => {
try {
setElements(JSON.parse(json).elements);
} catch (error) {
console.log(error);
} catch (e) {
setError(e.message);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -112,34 +117,76 @@ storiesOf(STORYBOOK_PATH, module).add(
return (
<div>
<Cytoscape elements={elements} height={600} width={1340} />
<EuiForm isInvalid={error !== undefined} error={error}>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCodeEditor
mode="json"
theme="github"
width="100%"
value={json}
setOptions={{ fontSize: '12px' }}
onChange={(value) => {
setJson(value);
}}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup direction="column">
<EuiFilePicker
display={'large'}
fullWidth={true}
style={{ height: '100%' }}
initialPromptText="Upload a JSON file"
onChange={(event) => {
const item = event?.item(0);
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
onClick={() => {
setElements(JSON.parse(json).elements);
setSessionJson(json);
}}
>
Render JSON
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiCodeEditor
mode="json"
theme="github"
width="100%"
value={json}
setOptions={{ fontSize: '12px' }}
onChange={(value) => {
setJson(value);
}}
/>
if (item) {
const f = new FileReader();
f.onload = (onloadEvent) => {
const result = onloadEvent?.target?.result;
if (typeof result === 'string') {
setJson(result);
}
};
f.readAsText(item);
}
}}
/>
<EuiSpacer />
<EuiButton
onClick={() => {
try {
setElements(JSON.parse(json).elements);
setSessionJson(json);
setError(undefined);
} catch (e) {
setError(e.message);
}
}}
>
Render JSON
</EuiButton>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
</div>
);
},
{
info: { propTables: false, source: false },
info: {
propTables: false,
source: false,
text: `
Enter JSON map data into the text box or upload a file and click "Render JSON" to see the results. You can enable a download button on the service map by putting
\`\`\`
sessionStorage.setItem('apm_debug', 'true')
\`\`\`
into the JavaScript console and reloading the page.`,
},
}
);