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

Sync main with Chris' testing revamp #622

Merged
merged 5 commits into from
Oct 11, 2023
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
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: [],
})
);
const { user } = renderCheckList();
const additionalFilters = await screen.findByRole('button', { name: /Additional Filters \(1 active\)/i });
await user.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: [],
})
);
const { user } = renderCheckList();
const additionalFilters = await screen.findByRole('button', { name: /Additional Filters \(1 active\)/i });
await user.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: [],
})
);
const { user } = renderCheckList();
const additionalFilters = await screen.findByRole('button', { name: /Additional Filters \(1 active\)/i });
await user.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