Skip to content

Commit

Permalink
PMM-9148 QAN share link (#1299)
Browse files Browse the repository at this point in the history
* PMM-9148 Update core package

* PMM-9148 QAN share link

* PMM-9148 Handle clipboard not available
  • Loading branch information
tiagomotasantos authored Feb 15, 2022
1 parent 1b036ef commit d2acb89
Show file tree
Hide file tree
Showing 9 changed files with 3,680 additions and 2,883 deletions.
6,421 changes: 3,551 additions & 2,870 deletions pmm-app/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pmm-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@grafana/data": "7.5.11",
"@grafana/runtime": "7.5.11",
"@grafana/ui": "7.5.11",
"@percona/platform-core": "^0.9.4",
"@percona/platform-core": "^0.12.0",
"@testing-library/jest-dom": "5.11.5",
"@testing-library/react": "11.1.2",
"@testing-library/react-hooks": "^3.2.1",
Expand Down Expand Up @@ -64,6 +64,7 @@
"scroll-into-view": "^1.14.2",
"simplebar-react": "^3.0.0-beta.8",
"sql-formatter": "^2.3.3",
"tslib": "^2.3.1",
"uuid": "^8.3.0",
"yaml": "^1.10.0"
},
Expand Down
4 changes: 4 additions & 0 deletions pmm-app/src/pmm-qan/panel/QueryAnalytics.messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ export const Messages = {
search: {
placeholder: 'Search by...',
},
copyLink: 'Copy Link',
copyLinkTooltip: 'Copies sharable Query Analytics link to clipboard',
copiedToClipboard: 'Successfully copied Query Analytics link to clipboard',
clipboardNotAvailable: 'Clipboard is not available. Copy the link:',
};
9 changes: 9 additions & 0 deletions pmm-app/src/pmm-qan/panel/QueryAnalytics.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export const getStyles = stylesFactory((theme: GrafanaTheme) => {
justify-content: flex-end;
padding: 13px 2px 5px 0px;
height: 50px;
button {
margin-right: ${theme.spacing.sm} !important;
}
`,
overviewFooter: css`
display: flex;
Expand Down Expand Up @@ -49,5 +53,10 @@ export const getStyles = stylesFactory((theme: GrafanaTheme) => {
height: 100%;
position: relative;
`,
link: css`
display: block;
margin-top: ${theme.spacing.sm};
text-decoration: underline;
`,
};
});
37 changes: 37 additions & 0 deletions pmm-app/src/pmm-qan/panel/QueryAnalytics.tools.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { buildShareLink, toUnixTimestamp } from './QueryAnalytics.tools';

jest.mock('shared/components/helpers/notification-manager');
jest.mock('@grafana/runtime', () => ({
config: { bootData: { user: { orgId: 1 } } },
}));

let windowSpy;

beforeEach(() => {
windowSpy = jest.spyOn(window, 'window', 'get');
});

afterEach(() => {
windowSpy.mockRestore();
});

describe('QueryAnalytics.tools::', () => {
test('toUnixTimestamp', () => {
expect(toUnixTimestamp('2022-01-17T11:07:29+00:00')).toBe(1642417649000);
expect(toUnixTimestamp('Mon, 17 Jan 2022 11:09:12 +0000')).toBe(1642417752000);
expect(toUnixTimestamp('Tue, 22 June 2021 23:30:10 +0100')).toBe(1624401010000);
});
test('buildShareLink', () => {
windowSpy.mockImplementation(() => ({
location: {
origin: 'https://localhost.com',
pathname: '/graph',
search: '?page_number=2&page_size=25',
},
}));
const result = buildShareLink(1624401010000, 1642417914429);
const expected = 'https://localhost.com/graph?page_number=2&page_size=25&from=1624401010000&to=1642417914429&orgId=1';

expect(result).toBe(expected);
});
});
19 changes: 19 additions & 0 deletions pmm-app/src/pmm-qan/panel/QueryAnalytics.tools.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { config } from '@grafana/runtime';
import { ALL_VARIABLE_TEXT } from './QueryAnalytics.constants';

export const getLabelQueryParams = (labels) => Object.keys(labels)
Expand All @@ -7,3 +8,21 @@ export const getLabelQueryParams = (labels) => Object.keys(labels)
value: labels[key],
}))
.filter((item) => item.value.filter((element) => element !== ALL_VARIABLE_TEXT).length) || [];

export const toUnixTimestamp = (date: string) => Math.floor(new Date(date).getTime());

export function buildShareLink(from: number, to: number) {
const {
origin,
pathname,
search,
} = window.location;
const baseUrl = origin + pathname;
const params = new URLSearchParams(search.substring(1));

params.set('from', `${from}`);
params.set('to', `${to}`);
params.set('orgId', config.bootData.user.orgId);

return `${baseUrl}?${params.toString()}`;
}
36 changes: 32 additions & 4 deletions pmm-app/src/pmm-qan/panel/QueryAnalytics.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, {
FC, useContext, useEffect, useRef, useState,
FC, useCallback, useContext, useEffect, useRef, useState,
} from 'react';
import SplitPane from 'react-split-pane';
import { useTheme } from '@grafana/ui';
import { cx } from 'emotion';
import { Button, useTheme } from '@grafana/ui';
import { showSuccessNotification, showWarningNotification } from 'shared/components/helpers';
import { QueryAnalyticsProvider, UrlParametersProvider } from './provider/provider';
import {
Details, Filters, ManageColumns, Overview,
Expand All @@ -12,19 +13,37 @@ import 'shared/styles.scss';
import 'shared/style.less';
import './qan.scss';
import { getStyles } from './QueryAnalytics.styles';
import { Messages } from './QueryAnalytics.messages';
import { buildShareLink, toUnixTimestamp } from './QueryAnalytics.tools';

const QueryAnalyticsPanel: FC = () => {
const theme = useTheme();
const styles = getStyles(theme);

const {
panelState: { querySelected },
panelState: { querySelected, from, to },
} = useContext(QueryAnalyticsProvider);
// TODO: replace with something more elegant & fast
const queryAnalyticsWrapper = useRef<HTMLDivElement>(null);
const size = queryAnalyticsWrapper.current?.clientWidth;

const [, setReload] = useState<object>({});
const copyLinkToClipboard = useCallback(() => {
const link = buildShareLink(toUnixTimestamp(from), toUnixTimestamp(to));

if (navigator && navigator.clipboard) {
navigator.clipboard.writeText(link);
showSuccessNotification({ message: Messages.copiedToClipboard });
} else {
const message = (
<div>
{Messages.clipboardNotAvailable}
<span className={styles.link}>{link}</span>
</div>
);

showWarningNotification({ message });
}
}, [from, to]);

useEffect(() => {
setReload({});
Expand All @@ -38,6 +57,15 @@ const QueryAnalyticsPanel: FC = () => {
<div className="query-analytics-data">
<div className={styles.getContainerWrapper(size)}>
<div className={cx(styles.overviewHeader, 'manage-columns')}>
<Button
onClick={copyLinkToClipboard}
variant="secondary"
icon="copy"
title={Messages.copyLinkTooltip}
data-testid="copy-link-button"
>
{Messages.copyLink}
</Button>
<ManageColumns onlyAdd />
</div>
<div className={styles.splitterWrapper}>
Expand Down
27 changes: 23 additions & 4 deletions pmm-app/src/pmm-qan/panel/provider/provider.tools.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { getLocationSrv } from '@grafana/runtime';
import { ParseQueryParamDate } from 'shared/components/helpers/time-parameters-parser';
import {
ALL_VARIABLE_TEXT, AUTO_VARIABLE_TEXT, DEFAULT_COLUMNS, DEFAULT_PAGE_SIZE, FILTERS_NAMES,
ALL_VARIABLE_TEXT,
AUTO_VARIABLE_TEXT,
DEFAULT_COLUMNS,
DEFAULT_PAGE_NUMBER,
DEFAULT_PAGE_SIZE,
FILTERS_NAMES,
} from '../QueryAnalytics.constants';

const setFilters = (query) => FILTERS_NAMES.reduce((acc, filterName) => {
Expand Down Expand Up @@ -37,7 +42,16 @@ interface GrafanaVariables {
}
export const refreshGrafanaVariables = (state) => {
const {
labels, columns, groupBy, queryId, orderBy, rawTime, dimensionSearchText, database,
labels,
columns,
groupBy,
queryId,
orderBy,
rawTime,
dimensionSearchText,
database,
pageNumber,
pageSize,
} = state;

const variablesQuery: GrafanaVariables = {};
Expand Down Expand Up @@ -95,6 +109,11 @@ export const refreshGrafanaVariables = (state) => {
variablesQuery.dimensionSearchText = dimensionSearchText;
}

if (pageNumber && pageSize) {
variablesQuery.page_number = pageNumber;
variablesQuery.page_size = pageSize;
}

variablesQuery.totals = String(state.totals);

if (state.querySelected) {
Expand All @@ -117,8 +136,8 @@ export const parseURL = (query) => ({
.format('YYYY-MM-DDTHH:mm:ssZ'),
columns: JSON.parse(query.get('columns')) || DEFAULT_COLUMNS,
labels: setFilters(query),
pageNumber: 1,
pageSize: DEFAULT_PAGE_SIZE,
pageNumber: query.get('page_number') || DEFAULT_PAGE_NUMBER,
pageSize: query.get('page_size') || DEFAULT_PAGE_SIZE,
orderBy: query.get('order_by') || `-${(JSON.parse(query.get('columns')) || DEFAULT_COLUMNS)[0]}`,
queryId: query.get('filter_by'),
database: query.get('selected_query_database'),
Expand Down
7 changes: 3 additions & 4 deletions pmm-app/src/pmm-qan/panel/provider/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,12 @@ export const UrlParametersProvider = ({ timeRange, children }) => {
refreshGrafanaVariables(panelState);
}, [panelState]);

const [from, setFrom] = useState(timeRange.raw.from);
const [to, setTo] = useState(timeRange.raw.to);
const getAbsoluteTime = (timeValue) => (timeValue.valueOf ? timeValue.valueOf() : timeValue);
const [from, setFrom] = useState(getAbsoluteTime(timeRange.raw.from));
const [to, setTo] = useState(getAbsoluteTime(timeRange.raw.to));
const [previousState, setPreviousState] = useState(panelState);

useEffect(() => {
const getAbsoluteTime = (timeValue) => (timeValue.valueOf ? timeValue.valueOf() : timeValue);

const newFrom = getAbsoluteTime(timeRange.raw.from);
const newTo = getAbsoluteTime(timeRange.raw.to);

Expand Down

0 comments on commit d2acb89

Please sign in to comment.