Skip to content

Commit

Permalink
Merge branch 'main' of github.com:grafana/synthetic-monitoring-app in…
Browse files Browse the repository at this point in the history
…to russ-test-test
  • Loading branch information
rdubrock committed Oct 11, 2023
2 parents b8464c4 + fa286bc commit 64bb815
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 26 deletions.
9 changes: 5 additions & 4 deletions dev/provisioning/datasources/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
# access: proxy
# url: https://logs-prod-us-central1.grafana.net
# basicAuth: true
# basicAuthUser: <instanceID>
# basicAuthPassword: <grafana.com api key>
# basicAuthUser: <Grafana Cloud Loki instance ID>
# jsonData:
# maxLines: 1000
# secureJsonData:
# basicAuthPassword: <viewer token from grafana.com>

# - name: grafanacloud-<orgslug>-prom
# type: prometheus
# access: proxy
# url: https://prometheus-us-central1.grafana.net/api/prom
# basicAuth: true
# basicAuthUser: <instanceID>
# basicAuthUser: <Grafana Cloud Prom instance ID>
# jsonData:
# timeInterval: 1s
# secureJsonData:
# basicAuthPassword: <grafana.com api key>
# basicAuthPassword: <viewer token from grafana.com>
12 changes: 12 additions & 0 deletions src/components/CheckFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ export const defaultFilters: CheckFiltersType = {
probes: [],
};

export const getDefaultFilters = (): CheckFiltersType => {
const storedFilters = localStorage.getItem('checkFilters');
if (storedFilters) {
try {
return JSON.parse(storedFilters) as CheckFiltersType;
} catch (e) {
return defaultFilters;
}
}
return defaultFilters;
};

export function CheckFilters({
handleResetFilters,
onChange,
Expand Down
26 changes: 23 additions & 3 deletions src/components/CheckList/CheckFilterGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Button, Icon, useStyles2 } from '@grafana/ui';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { CheckFiltersType } from 'types';
import { defaultFilters } from 'components/CheckFilters';

const groupStyles = (theme: GrafanaTheme2) => ({
container: css`
Expand Down Expand Up @@ -58,8 +57,29 @@ const CheckFilterGroup = ({ children, onReset, filters }: Props) => {
// Count which filters have been applied
Object.keys(filters).forEach((key) => {
// Search filter is handled separately
if (key !== 'search' && filters[key] !== defaultFilters[key]) {
active += 1;
switch (key) {
case 'labels':
if (filters.labels.length > 0) {
active += 1;
}
break;
case 'search':
break;
case 'status':
if (filters.status.value !== 0) {
active += 1;
}
break;
case 'probes':
if (filters.probes.length > 0) {
active += 1;
}
break;
case 'type':
if (filters.type !== 'all') {
active += 1;
}
break;
}
});
setActiveFilters(active);
Expand Down
86 changes: 86 additions & 0 deletions src/components/CheckList/CheckList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ const renderCheckList = ({ checks = defaultChecks } = {} as RenderChecklist) =>
);
};

beforeEach(() => {
localStorage.clear();
});

test('renders empty state', async () => {
renderCheckList({ checks: [] });
const emptyWarning = await screen.findByText('This account does not currently have any checks configured', {
Expand Down Expand Up @@ -251,6 +255,88 @@ test('filters by probe', async () => {
expect(checks.length).toBe(2);
});

test('loads search from localStorage', async () => {
localStorage.setItem(
'checkFilters',
JSON.stringify({
search: 'chimichurri',
labels: [],
type: 'all',
status: { label: 'All', value: 0 },
probes: [],
})
);
renderCheckList();
const searchInput = await screen.findByPlaceholderText('Search by job name, endpoint, or label');
expect(searchInput).toHaveValue('chimichurri');

const checks = await screen.findAllByTestId('check-card');
expect(checks.length).toBe(1);
});

test('loads status filter from localStorage', async () => {
localStorage.setItem(
'checkFilters',
JSON.stringify({
search: '',
labels: [],
type: 'all',
status: { label: 'Disabled', value: 2 },
probes: [],
})
);
renderCheckList();
const additionalFilters = await screen.findByRole('button', { name: 'Additional Filters (1 active)', exact: false });
userEvent.click(additionalFilters);
const statusFilter = await screen.findByTestId('check-status-filter');
expect(statusFilter).toHaveValue('2');

const checks = await screen.findAllByTestId('check-card');
expect(checks.length).toBe(1);
});

test('loads type filter from localStorage', async () => {
localStorage.setItem(
'checkFilters',
JSON.stringify({
search: '',
labels: [],
type: 'http',
status: { label: 'All', value: 0 },
probes: [],
})
);
renderCheckList();
const additionalFilters = await screen.findByRole('button', { name: 'Additional Filters (1 active)', exact: false });
userEvent.click(additionalFilters);
const typeFilter = await screen.findByTestId('check-type-filter');
expect(typeFilter).toHaveValue('http');

const checks = await screen.findAllByTestId('check-card');
expect(checks.length).toBe(1);
});

test('loads labels from localStorage', async () => {
localStorage.setItem(
'checkFilters',
JSON.stringify({
search: '',
labels: ['agreat: label'],
type: 'all',
status: { label: 'All', value: 0 },
probes: [],
})
);
renderCheckList();
const additionalFilters = await screen.findByRole('button', { name: 'Additional Filters (1 active)', exact: false });
userEvent.click(additionalFilters);
const filterInput = await screen.findByTestId('check-label-filter');
expect(filterInput).toHaveValue(['agreat: label']);

const checks = await screen.findAllByTestId('check-card');
expect(checks.length).toBe(1);
});

test('clicking type chiclet adds it to filter', async () => {
const { user } = renderCheckList();
const additionalFilters = await screen.findByRole('button', { name: 'Additional Filters' });
Expand Down
18 changes: 13 additions & 5 deletions src/components/CheckList/CheckList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AppEvents, GrafanaTheme2, OrgRole, SelectableValue } from '@grafana/dat
import { config } from '@grafana/runtime';
import { Button, ButtonCascader, Checkbox, Icon, InlineSwitch, Pagination, Select, useStyles2 } from '@grafana/ui';
import { BulkEditModal } from 'components/BulkEditModal';
import { CheckFilters, defaultFilters } from 'components/CheckFilters';
import { CheckFilters, defaultFilters, getDefaultFilters } from 'components/CheckFilters';
import { ChecksContextProvider } from 'components/ChecksContextProvider';
import { PluginPage } from 'components/PluginPage';
import { SuccessRateContext, SuccessRateTypes } from 'contexts/SuccessRateContext';
Expand Down Expand Up @@ -113,7 +113,7 @@ interface Props {
}

export const CheckList = ({ instance, checks, onCheckUpdate }: Props) => {
const [checkFilters, setCheckFilters] = useState<CheckFiltersType>(defaultFilters);
const [checkFilters, setCheckFilters] = useState<CheckFiltersType>(getDefaultFilters());
const [filteredChecks, setFilteredChecks] = useState<FilteredCheck[] | []>([]);

const [currentPage, setCurrentPage] = useState(1);
Expand Down Expand Up @@ -177,21 +177,26 @@ export const CheckList = ({ instance, checks, onCheckUpdate }: Props) => {

const handleResetFilters = () => {
setCheckFilters(defaultFilters);
localStorage.removeItem('checkFilters');
};

const handleLabelSelect = (label: Label) => {
setCheckFilters((cf) => {
return {
const updated = {
...cf,
labels: [...cf.labels, `${label.name}: ${label.value}`],
};
localStorage.setItem('checkFilters', JSON.stringify(updated));
return updated;
});
setCurrentPage(1);
};

const handleTypeSelect = (checkType: CheckType) => {
setCheckFilters((cf) => {
return { ...cf, type: checkType };
const updated = { ...cf, type: checkType };
localStorage.setItem('checkFilters', JSON.stringify(updated));
return updated;
});
setCurrentPage(1);
};
Expand All @@ -201,10 +206,12 @@ export const CheckList = ({ instance, checks, onCheckUpdate }: Props) => {
const option = CHECK_LIST_STATUS_OPTIONS.find(({ value }) => value === status);
if (option) {
setCheckFilters((cf) => {
return {
const updated = {
...cf,
status: option,
};
localStorage.setItem('checkFilters', JSON.stringify(updated));
return updated;
});
setCurrentPage(1);
}
Expand Down Expand Up @@ -318,6 +325,7 @@ export const CheckList = ({ instance, checks, onCheckUpdate }: Props) => {
checkFilters={checkFilters}
onChange={(filters: CheckFiltersType) => {
setCheckFilters(filters);
localStorage.setItem('checkFilters', JSON.stringify(filters));
}}
/>
{hasRole(OrgRole.Editor) && (
Expand Down
10 changes: 8 additions & 2 deletions src/components/CheckList/checkFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@ const matchesSearchFilter = ({ target, job, labels }: Check, searchFilter: strin
};

const matchesLabelFilter = ({ labels }: Check, labelFilters: string[]) => {
if (labelFilters.length === 0) {
if (!labelFilters || labelFilters.length === 0) {
return true;
}
return labels.some(({ name, value }) => labelFilters.some((filter) => filter === `${name}: ${value}`));
const result = labels?.some(({ name, value }) => {
const filtersResult = labelFilters.some((filter) => {
return filter === `${name}: ${value}`;
});
return filtersResult;
});
return result;
};

const matchesStatusFilter = ({ enabled }: Check, { value }: SelectableValue) => {
Expand Down
22 changes: 20 additions & 2 deletions src/components/CheckTestButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useContext, useMemo, useState } from 'react';
import { Button, Spinner } from '@grafana/ui';
import { Alert, Button, Modal, Spinner } from '@grafana/ui';
import { CheckTestResultsModal } from './CheckTestResultsModal';
import { AdHocCheckResponse, Check, CheckFormValues, CheckType } from 'types';
import { useFormContext } from 'react-hook-form';
Expand All @@ -14,6 +14,8 @@ interface Props {

export function CheckTestButton({ check }: Props) {
const [isTestModalOpen, setTestModalOpen] = useState(false);
const [isErrorModalOpen, setErrorModalOpen] = useState(false);
const [error, setError] = useState('');
const [testResponse, setTestResponse] = useState<AdHocCheckResponse>();
const [testRequestInFlight, setTestRequestInFlight] = useState(false);
const defaultValues = useMemo(() => getDefaultValuesFromCheck(check), [check]);
Expand All @@ -25,7 +27,7 @@ export function CheckTestButton({ check }: Props) {
<Button
type="button"
variant="secondary"
disabled={testRequestInFlight || checkType === CheckType.Traceroute || !formMethods.formState.isValid}
disabled={testRequestInFlight || checkType === CheckType.Traceroute}
onClick={() => {
const values = formMethods.getValues() as CheckFormValues;
const check = getCheckFromFormValues(values, defaultValues, checkType);
Expand All @@ -37,6 +39,10 @@ export function CheckTestButton({ check }: Props) {
setTestModalOpen(true);
setTestResponse(resp);
})
.catch((err) => {
setErrorModalOpen(true);
setError(err?.data?.err ?? err?.data?.msg);
})
.finally(() => {
setTestRequestInFlight(false);
});
Expand All @@ -52,6 +58,18 @@ export function CheckTestButton({ check }: Props) {
}}
testResponse={testResponse}
/>
<Modal
isOpen={isErrorModalOpen}
title="Error testing check"
onDismiss={() => {
setErrorModalOpen(false);
setError('');
}}
>
<Alert severity="error" title="Something went wrong running the check">
{error}
</Alert>
</Modal>
</>
);
}
11 changes: 8 additions & 3 deletions src/components/CheckTestResultsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface Props {
}

function buildLogsDf(logs: Array<Record<string, any>>) {
const tsValues = new Array(logs.length).fill(Date.now());
const tsValues = new Array(logs?.length).fill(Date.now());
const fields: Record<string, any> = {
// a timestamp is required for the panel to render. we don't care about/have a timestamp so we just fill with the time for right now
ts: {
Expand Down Expand Up @@ -119,7 +119,12 @@ export function CheckTestResultsModal({ testResponse, isOpen, onDismiss }: Props
const logsStr = item.values?.[0]?.[1];
try {
const info = JSON.parse(logsStr);
const df = buildLogsDf(info.logs);
let df;
if (!info.logs) {
df = buildLogsDf([{ level: 'error', msg: 'There was an error running the test' }]);
} else {
df = buildLogsDf(info.logs);
}
info.logs = df;
if (!resultsByProbe[`${info.probe}${testResponse.id}`] && info.id === testResponse.id) {
setResultsByProbe({ ...resultsByProbe, [`${info.probe}${testResponse.id}`]: info });
Expand All @@ -140,7 +145,7 @@ export function CheckTestResultsModal({ testResponse, isOpen, onDismiss }: Props
}}
>
<p>Tests will run on up to 5 randomly selected probes</p>
{testResponse?.probes.map((testProbe) => {
{testResponse?.probes?.map((testProbe) => {
const probe = probes?.find((probe) => probe.id === testProbe);
const resultKey = `${probe?.name}${testResponse.id}`;
const result = resultsByProbe[resultKey];
Expand Down
2 changes: 2 additions & 0 deletions src/components/MultiHttp/MultiHttpSettingsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { FaroEvent, reportEvent, reportError } from 'faro';
import { CheckFormAlert } from 'components/CheckFormAlert';
import { HorizontalCheckboxField } from 'components/HorizonalCheckboxField';
import { CheckUsage } from 'components/CheckUsage';
import { CheckTestButton } from 'components/CheckTestButton';

interface Props {
checks?: Check[];
Expand Down Expand Up @@ -292,6 +293,7 @@ export const MultiHttpSettingsForm = ({ checks, onReturn }: Props) => {
<Button type="submit" disabled={formMethods.formState.isSubmitting || submitting}>
Save
</Button>
<CheckTestButton check={check} />
{check?.id && (
<Button
variant="destructive"
Expand Down
4 changes: 0 additions & 4 deletions src/datasource/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,6 @@ export class SMDataSource extends DataSourceApi<SMQuery, SMOptions> {
}

async testCheck(check: Check): Promise<any> {
if (check.timeout > 2500) {
check.timeout = 2500;
}

const randomSelection = getRandomProbes(check.probes, 5);
check.probes = randomSelection;

Expand Down
Loading

0 comments on commit 64bb815

Please sign in to comment.