123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- import { render, screen, waitFor } from '@testing-library/react';
- import userEvent from '@testing-library/user-event';
- import React from 'react';
- import { createTheme } from '@grafana/data';
- import LokiLanguageProvider from '../language_provider';
- import {
- buildSelector,
- facetLabels,
- SelectableLabel,
- UnthemedLokiLabelBrowser,
- BrowserProps,
- } from './LokiLabelBrowser';
- // we have to mock out reportInteraction, otherwise it crashes the test.
- jest.mock('@grafana/runtime', () => ({
- ...jest.requireActual('@grafana/runtime'),
- reportInteraction: () => null,
- }));
- describe('buildSelector()', () => {
- it('returns an empty selector for no labels', () => {
- expect(buildSelector([])).toEqual('{}');
- });
- it('returns an empty selector for selected labels with no values', () => {
- const labels: SelectableLabel[] = [{ name: 'foo', selected: true }];
- expect(buildSelector(labels)).toEqual('{}');
- });
- it('returns an empty selector for one selected label with no selected values', () => {
- const labels: SelectableLabel[] = [{ name: 'foo', selected: true, values: [{ name: 'bar' }] }];
- expect(buildSelector(labels)).toEqual('{}');
- });
- it('returns a simple selector from a selected label with a selected value', () => {
- const labels: SelectableLabel[] = [{ name: 'foo', selected: true, values: [{ name: 'bar', selected: true }] }];
- expect(buildSelector(labels)).toEqual('{foo="bar"}');
- });
- });
- describe('facetLabels()', () => {
- const possibleLabels = {
- cluster: ['dev'],
- namespace: ['alertmanager'],
- };
- const labels: SelectableLabel[] = [
- { name: 'foo', selected: true, values: [{ name: 'bar' }] },
- { name: 'cluster', values: [{ name: 'dev' }, { name: 'ops' }, { name: 'prod' }] },
- { name: 'namespace', values: [{ name: 'alertmanager' }] },
- ];
- it('returns no labels given an empty label set', () => {
- expect(facetLabels([], {})).toEqual([]);
- });
- it('marks all labels as hidden when no labels are possible', () => {
- const result = facetLabels(labels, {});
- expect(result.length).toEqual(labels.length);
- expect(result[0].hidden).toBeTruthy();
- expect(result[0].values).toBeUndefined();
- });
- it('keeps values as facetted when they are possible', () => {
- const result = facetLabels(labels, possibleLabels);
- expect(result.length).toEqual(labels.length);
- expect(result[0].hidden).toBeTruthy();
- expect(result[0].values).toBeUndefined();
- expect(result[1].hidden).toBeFalsy();
- expect(result[1].values!.length).toBe(1);
- expect(result[1].values![0].name).toBe('dev');
- });
- it('does not facet out label values that are currently being facetted', () => {
- const result = facetLabels(labels, possibleLabels, 'cluster');
- expect(result.length).toEqual(labels.length);
- expect(result[0].hidden).toBeTruthy();
- expect(result[1].hidden).toBeFalsy();
- // 'cluster' is being facetted, should show all 3 options even though only 1 is possible
- expect(result[1].values!.length).toBe(3);
- expect(result[2].values!.length).toBe(1);
- });
- });
- describe('LokiLabelBrowser', () => {
- const setupProps = (): BrowserProps => {
- const mockLanguageProvider = {
- start: () => Promise.resolve(),
- getLabelValues: (name: string) => {
- switch (name) {
- case 'label1':
- return ['value1-1', 'value1-2'];
- case 'label2':
- return ['value2-1', 'value2-2'];
- case 'label3':
- return ['value3-1', 'value3-2'];
- }
- return [];
- },
- fetchSeriesLabels: (selector: string) => {
- switch (selector) {
- case '{label1="value1-1"}':
- return { label1: ['value1-1'], label2: ['value2-1'], label3: ['value3-1'] };
- case '{label1=~"value1-1|value1-2"}':
- return { label1: ['value1-1', 'value1-2'], label2: ['value2-1'], label3: ['value3-1', 'value3-2'] };
- }
- // Allow full set by default
- return {
- label1: ['value1-1', 'value1-2'],
- label2: ['value2-1', 'value2-2'],
- };
- },
- getLabelKeys: () => ['label1', 'label2', 'label3'],
- };
- const defaults: BrowserProps = {
- theme: createTheme(),
- onChange: () => {},
- autoSelect: 0,
- languageProvider: mockLanguageProvider as unknown as LokiLanguageProvider,
- lastUsedLabels: [],
- storeLastUsedLabels: () => {},
- deleteLastUsedLabels: () => {},
- };
- return defaults;
- };
- // Clear label selection manually because it's saved in localStorage
- afterEach(async () => {
- const clearBtn = screen.getByLabelText('Selector clear button');
- await userEvent.click(clearBtn);
- });
- it('renders and loader shows when empty, and then first set of labels', async () => {
- const props = setupProps();
- render(<UnthemedLokiLabelBrowser {...props} />);
- // Loading appears and dissappears
- screen.getByText(/Loading labels/);
- await waitFor(() => {
- expect(screen.queryByText(/Loading labels/)).not.toBeInTheDocument();
- });
- // Initial set of labels is available and not selected
- expect(screen.queryByRole('option', { name: 'label1' })).toBeInTheDocument();
- expect(screen.queryByRole('option', { name: 'label1', selected: true })).not.toBeInTheDocument();
- expect(screen.queryByRole('option', { name: 'label2' })).toBeInTheDocument();
- expect(screen.queryByRole('option', { name: 'label2', selected: true })).not.toBeInTheDocument();
- expect(screen.queryByLabelText('selector')).toHaveTextContent('{}');
- });
- it('allows label and value selection/deselection', async () => {
- const props = setupProps();
- render(<UnthemedLokiLabelBrowser {...props} />);
- // Selecting label2
- const label2 = await screen.findByRole('option', { name: 'label2', selected: false });
- expect(screen.queryByRole('list', { name: /Values/ })).not.toBeInTheDocument();
- await userEvent.click(label2);
- expect(screen.queryByRole('option', { name: 'label2', selected: true })).toBeInTheDocument();
- // List of values for label2 appears
- expect(await screen.findAllByRole('list')).toHaveLength(1);
- expect(screen.queryByLabelText(/Values for/)).toHaveTextContent('label2');
- expect(screen.queryByRole('option', { name: 'value2-1' })).toBeInTheDocument();
- expect(screen.queryByRole('option', { name: 'value2-2' })).toBeInTheDocument();
- expect(screen.queryByLabelText('selector')).toHaveTextContent('{}');
- // Selecting label1, list for its values appears
- const label1 = await screen.findByRole('option', { name: 'label1', selected: false });
- await userEvent.click(label1);
- expect(screen.queryByRole('option', { name: 'label1', selected: true })).toBeInTheDocument();
- await screen.findByLabelText('Values for label1');
- expect(await screen.findAllByRole('list')).toHaveLength(2);
- // Selecting value2-2 of label2
- const value = await screen.findByRole('option', { name: 'value2-2', selected: false });
- await userEvent.click(value);
- await screen.findByRole('option', { name: 'value2-2', selected: true });
- expect(screen.queryByLabelText('selector')).toHaveTextContent('{label2="value2-2"}');
- // Selecting value2-1 of label2, both values now selected
- const value2 = await screen.findByRole('option', { name: 'value2-1', selected: false });
- await userEvent.click(value2);
- // await screen.findByRole('option', {name: 'value2-1', selected: true});
- await screen.findByText('{label2=~"value2-1|value2-2"}');
- // Deselecting value2-2, one value should remain
- const selectedValue = await screen.findByRole('option', { name: 'value2-2', selected: true });
- await userEvent.click(selectedValue);
- await screen.findByRole('option', { name: 'value2-1', selected: true });
- await screen.findByRole('option', { name: 'value2-2', selected: false });
- expect(screen.queryByLabelText('selector')).toHaveTextContent('{label2="value2-1"}');
- });
- it('allows label selection from multiple labels', async () => {
- const props = setupProps();
- render(<UnthemedLokiLabelBrowser {...props} />);
- // Selecting label2
- const label2 = await screen.findByRole('option', { name: /label2/, selected: false });
- await userEvent.click(label2);
- // List of values for label2 appears
- expect(screen.queryByLabelText(/Values for/)).toHaveTextContent('label2');
- expect(screen.queryByRole('option', { name: 'value2-1' })).toBeInTheDocument();
- expect(screen.queryByRole('option', { name: 'value2-2' })).toBeInTheDocument();
- expect(screen.queryByLabelText('selector')).toHaveTextContent('{}');
- // Selecting label1, list for its values appears
- const label1 = await screen.findByRole('option', { name: 'label1', selected: false });
- await userEvent.click(label1);
- await screen.findByLabelText('Values for label1');
- expect(await screen.findAllByRole('list')).toHaveLength(2);
- // Selecting value2-1 of label2
- const value2 = await screen.findByRole('option', { name: 'value2-1', selected: false });
- await userEvent.click(value2);
- await screen.findByText('{label2="value2-1"}');
- // Selecting value from label1 for combined selector
- const value1 = await screen.findByRole('option', { name: 'value1-2', selected: false });
- await userEvent.click(value1);
- await screen.findByRole('option', { name: 'value1-2', selected: true });
- await screen.findByText('{label1="value1-2",label2="value2-1"}');
- // Deselect label1 should remove label and value
- const selectedLabel = (await screen.findAllByRole('option', { name: /label1/, selected: true }))[0];
- await userEvent.click(selectedLabel);
- await screen.findByRole('option', { name: /label1/, selected: false });
- expect(await screen.findAllByRole('list')).toHaveLength(1);
- expect(screen.queryByLabelText('selector')).toHaveTextContent('{label2="value2-1"}');
- });
- it('allows clearing the label selection', async () => {
- const props = setupProps();
- render(<UnthemedLokiLabelBrowser {...props} />);
- // Selecting label2
- const label2 = await screen.findByRole('option', { name: 'label2', selected: false });
- await userEvent.click(label2);
- // List of values for label2 appears
- expect(screen.queryByLabelText(/Values for/)).toHaveTextContent('label2');
- expect(screen.queryByRole('option', { name: 'value2-1' })).toBeInTheDocument();
- expect(screen.queryByRole('option', { name: 'value2-2' })).toBeInTheDocument();
- expect(screen.queryByLabelText('selector')).toHaveTextContent('{}');
- // Selecting label1, list for its values appears
- const label1 = await screen.findByRole('option', { name: 'label1', selected: false });
- await userEvent.click(label1);
- await screen.findByLabelText('Values for label1');
- expect(await screen.findAllByRole('list')).toHaveLength(2);
- // Selecting value2-1 of label2
- const value2 = await screen.findByRole('option', { name: 'value2-1', selected: false });
- await userEvent.click(value2);
- await screen.findByText('{label2="value2-1"}');
- // Clear selector
- const clearBtn = screen.getByLabelText('Selector clear button');
- await userEvent.click(clearBtn);
- await screen.findByRole('option', { name: 'label2', selected: false });
- expect(screen.queryByLabelText('selector')).toHaveTextContent('{}');
- });
- it('filters values by input text', async () => {
- const props = setupProps();
- render(<UnthemedLokiLabelBrowser {...props} />);
- // Selecting label2 and label1
- const label2 = await screen.findByRole('option', { name: /label2/, selected: false });
- await userEvent.click(label2);
- const label1 = await screen.findByRole('option', { name: /label1/, selected: false });
- await userEvent.click(label1);
- await screen.findByLabelText('Values for label1');
- await screen.findByLabelText('Values for label2');
- expect(await screen.findAllByRole('option', { name: /value/ })).toHaveLength(4);
- // Typing '1' to filter for values
- await userEvent.type(screen.getByLabelText('Filter expression for values'), 'val1');
- expect(screen.getByLabelText('Filter expression for values')).toHaveValue('val1');
- expect(screen.queryByRole('option', { name: 'value2-2' })).not.toBeInTheDocument();
- expect(await screen.findAllByRole('option', { name: /value/ })).toHaveLength(3);
- });
- it('facets labels', async () => {
- const props = setupProps();
- render(<UnthemedLokiLabelBrowser {...props} />);
- // Selecting label2 and label1
- const label2 = await screen.findByRole('option', { name: /label2/, selected: false });
- await userEvent.click(label2);
- const label1 = await screen.findByRole('option', { name: /label1/, selected: false });
- await userEvent.click(label1);
- await screen.findByLabelText('Values for label1');
- await screen.findByLabelText('Values for label2');
- expect(await screen.findAllByRole('option', { name: /value/ })).toHaveLength(4);
- expect(screen.queryByRole('option', { name: /label3/ })).toHaveTextContent('label3');
- // Click value1-1 which triggers facetting for value3-x, and still show all value1-x
- const value1 = await screen.findByRole('option', { name: 'value1-1', selected: false });
- await userEvent.click(value1);
- await waitFor(() => expect(screen.queryByRole('option', { name: 'value2-2' })).not.toBeInTheDocument());
- expect(screen.queryByRole('option', { name: 'value1-2' })).toBeInTheDocument();
- expect(screen.queryByLabelText('selector')).toHaveTextContent('{label1="value1-1"}');
- expect(screen.queryByRole('option', { name: /label3/ })).toHaveTextContent('label3 (1)');
- // Click value1-2 for which facetting will allow more values for value3-x
- const value12 = await screen.findByRole('option', { name: 'value1-2', selected: false });
- await userEvent.click(value12);
- await screen.findByRole('option', { name: 'value1-2', selected: true });
- await userEvent.click(screen.getByRole('option', { name: /label3/ }));
- await screen.findByLabelText('Values for label3');
- expect(screen.queryByRole('option', { name: 'value1-1', selected: true })).toBeInTheDocument();
- expect(screen.queryByRole('option', { name: 'value1-2', selected: true })).toBeInTheDocument();
- expect(screen.queryByLabelText('selector')).toHaveTextContent('{label1=~"value1-1|value1-2"}');
- expect(screen.queryAllByRole('option', { name: /label3/ })[0]).toHaveTextContent('label3 (2)');
- });
- });
|