Skip to content
Snippets Groups Projects
Commit 6aa03245 authored by Miguel Rincon's avatar Miguel Rincon
Browse files

Add tags filters to runners search

This change allow the user to filter by tags in the
runner UI when using the GraphQL API.
parent a6b764b6
No related branches found
No related tags found
No related merge requests found
Showing
with 491 additions and 83 deletions
<script>
import { GlFilteredSearchToken } from '@gitlab/ui';
import { cloneDeep } from 'lodash';
import { __, s__ } from '~/locale';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import {
STATUS_ACTIVE,
STATUS_PAUSED,
Loading
Loading
@@ -19,50 +19,9 @@ import {
CONTACTED_ASC,
PARAM_KEY_STATUS,
PARAM_KEY_RUNNER_TYPE,
PARAM_KEY_TAG,
} from '../constants';
const searchTokens = [
{
icon: 'status',
title: __('Status'),
type: PARAM_KEY_STATUS,
token: GlFilteredSearchToken,
// TODO Get more than one value when GraphQL API supports OR for "status"
unique: true,
options: [
{ value: STATUS_ACTIVE, title: s__('Runners|Active') },
{ value: STATUS_PAUSED, title: s__('Runners|Paused') },
{ value: STATUS_ONLINE, title: s__('Runners|Online') },
{ value: STATUS_OFFLINE, title: s__('Runners|Offline') },
// Added extra quotes in this title to avoid splitting this value:
// see: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1438
{ value: STATUS_NOT_CONNECTED, title: `"${s__('Runners|Not connected')}"` },
],
// TODO In principle we could support more complex search rules,
// this can be added to a separate issue.
operators: OPERATOR_IS_ONLY,
},
{
icon: 'file-tree',
title: __('Type'),
type: PARAM_KEY_RUNNER_TYPE,
token: GlFilteredSearchToken,
// TODO Get more than one value when GraphQL API supports OR for "status"
unique: true,
options: [
{ value: INSTANCE_TYPE, title: s__('Runners|shared') },
{ value: GROUP_TYPE, title: s__('Runners|group') },
{ value: PROJECT_TYPE, title: s__('Runners|specific') },
],
// TODO We should support more complex search rules,
// search for multiple states (OR) or have NOT operators
operators: OPERATOR_IS_ONLY,
},
// TODO Support tags
];
import TagToken from './search_tokens/tag_token.vue';
 
const sortOptions = [
{
Loading
Loading
@@ -95,6 +54,10 @@ export default {
return Array.isArray(val?.filters) && typeof val?.sort === 'string';
},
},
namespace: {
type: String,
required: true,
},
},
data() {
// filtered_search_bar_root.vue may mutate the inital
Loading
Loading
@@ -106,6 +69,57 @@ export default {
initialSortBy: sort,
};
},
computed: {
searchTokens() {
return [
{
icon: 'status',
title: __('Status'),
type: PARAM_KEY_STATUS,
token: BaseToken,
unique: true,
options: [
{ value: STATUS_ACTIVE, title: s__('Runners|Active') },
{ value: STATUS_PAUSED, title: s__('Runners|Paused') },
{ value: STATUS_ONLINE, title: s__('Runners|Online') },
{ value: STATUS_OFFLINE, title: s__('Runners|Offline') },
// Added extra quotes in this title to avoid splitting this value:
// see: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1438
{ value: STATUS_NOT_CONNECTED, title: `"${s__('Runners|Not connected')}"` },
],
// TODO In principle we could support more complex search rules,
// this can be added to a separate issue.
operators: OPERATOR_IS_ONLY,
},
{
icon: 'file-tree',
title: __('Type'),
type: PARAM_KEY_RUNNER_TYPE,
token: BaseToken,
unique: true,
options: [
{ value: INSTANCE_TYPE, title: s__('Runners|shared') },
{ value: GROUP_TYPE, title: s__('Runners|group') },
{ value: PROJECT_TYPE, title: s__('Runners|specific') },
],
// TODO We should support more complex search rules,
// search for multiple states (OR) or have NOT operators
operators: OPERATOR_IS_ONLY,
},
{
icon: 'tag',
title: s__('Runners|Tags'),
type: PARAM_KEY_TAG,
token: TagToken,
recentTokenValuesStorageKey: `${this.namespace}-recent-tags`,
operators: OPERATOR_IS_ONLY,
},
];
},
},
methods: {
onFilter(filters) {
const { sort } = this.value;
Loading
Loading
@@ -127,17 +141,17 @@ export default {
},
},
sortOptions,
searchTokens,
};
</script>
<template>
<filtered-search
v-bind="$attrs"
:namespace="namespace"
recent-searches-storage-key="runners-search"
:sort-options="$options.sortOptions"
:initial-filter-value="initialFilterValue"
:initial-sort-by="initialSortBy"
:tokens="$options.searchTokens"
:tokens="searchTokens"
:search-input-placeholder="__('Search or filter results...')"
@onFilter="onFilter"
@onSort="onSort"
Loading
Loading
<script>
import { GlBadge } from '@gitlab/ui';
import { RUNNER_TAG_BADGE_VARIANT } from '../constants';
export default {
components: {
GlBadge,
},
props: {
tag: {
type: String,
required: true,
},
size: {
type: String,
required: false,
default: 'md',
},
},
RUNNER_TAG_BADGE_VARIANT,
};
</script>
<template>
<gl-badge :size="size" :variant="$options.RUNNER_TAG_BADGE_VARIANT">
{{ tag }}
</gl-badge>
</template>
<script>
import { GlBadge } from '@gitlab/ui';
import RunnerTag from './runner_tag.vue';
 
export default {
components: {
GlBadge,
RunnerTag,
},
props: {
tagList: {
Loading
Loading
@@ -16,18 +16,11 @@ export default {
required: false,
default: 'md',
},
variant: {
type: String,
required: false,
default: 'info',
},
},
};
</script>
<template>
<div>
<gl-badge v-for="tag in tagList" :key="tag" :size="size" :variant="variant">
{{ tag }}
</gl-badge>
<runner-tag v-for="tag in tagList" :key="tag" :tag="tag" :size="size" />
</div>
</template>
<script>
import { GlFilteredSearchSuggestion, GlToken } from '@gitlab/ui';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import { RUNNER_TAG_BG_CLASS } from '../../constants';
export const TAG_SUGGESTIONS_PATH = '/admin/runners/tag_list.json';
export default {
components: {
BaseToken,
GlFilteredSearchSuggestion,
GlToken,
},
props: {
config: {
type: Object,
required: true,
},
},
data() {
return {
tags: [],
loading: false,
};
},
methods: {
fnCurrentTokenValue(data) {
// By default, values are transformed with `toLowerCase`
// however, runner tags are case sensitive.
return data;
},
getTagsOptions(search) {
// TODO This should be implemented via a GraphQL API
// The API should
// 1) scope to the rights of the user
// 2) stay up to date to the removal of old tags
// See: https://gitlab.com/gitlab-org/gitlab/-/issues/333796
return axios
.get(TAG_SUGGESTIONS_PATH, {
params: {
search,
},
})
.then(({ data }) => {
return data.map(({ id, name }) => ({ id, value: name, text: name }));
});
},
async fetchTags(searchTerm) {
this.loading = true;
try {
this.tags = await this.getTagsOptions(searchTerm);
} catch {
createFlash({
message: s__('Runners|Something went wrong while fetching the tags suggestions'),
});
} finally {
this.loading = false;
}
},
},
RUNNER_TAG_BG_CLASS,
};
</script>
<template>
<base-token
v-bind="$attrs"
:config="config"
:suggestions-loading="loading"
:suggestions="tags"
:fn-current-token-value="fnCurrentTokenValue"
:recent-suggestions-storage-key="config.recentTokenValuesStorageKey"
@fetch-suggestions="fetchTags"
v-on="$listeners"
>
<template #view-token="{ viewTokenProps: { listeners, inputValue, activeTokenValue } }">
<gl-token variant="search-value" :class="$options.RUNNER_TAG_BG_CLASS" v-on="listeners">
{{ activeTokenValue ? activeTokenValue.text : inputValue }}
</gl-token>
</template>
<template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion v-for="tag in suggestions" :key="tag.id" :value="tag.value">
{{ tag.text }}
</gl-filtered-search-suggestion>
</template>
</base-token>
</template>
Loading
Loading
@@ -6,13 +6,18 @@ export const I18N_DETAILS_TITLE = s__('Runners|Runner #%{runner_id}');
 
export const RUNNER_ENTITY_TYPE = 'Ci::Runner';
 
export const RUNNER_TAG_BADGE_VARIANT = 'info';
export const RUNNER_TAG_BG_CLASS = 'gl-bg-blue-100';
// Filtered search parameter names
// - Used for URL params names
// - GlFilteredSearch tokens type
 
export const PARAM_KEY_SEARCH = 'search';
export const PARAM_KEY_STATUS = 'status';
export const PARAM_KEY_RUNNER_TYPE = 'runner_type';
export const PARAM_KEY_TAG = 'tag';
export const PARAM_KEY_SEARCH = 'search';
export const PARAM_KEY_SORT = 'sort';
export const PARAM_KEY_PAGE = 'page';
export const PARAM_KEY_AFTER = 'after';
Loading
Loading
Loading
Loading
@@ -6,9 +6,10 @@ query getRunners(
$after: String
$first: Int
$last: Int
$search: String
$status: CiRunnerStatus
$type: CiRunnerType
$tagList: [String!]
$search: String
$sort: CiRunnerSort
) {
runners(
Loading
Loading
@@ -16,9 +17,10 @@ query getRunners(
after: $after
first: $first
last: $last
search: $search
status: $status
type: $type
tagList: $tagList
search: $search
sort: $sort
) {
nodes {
Loading
Loading
Loading
Loading
@@ -6,9 +6,10 @@ import {
prepareTokens,
} from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import {
PARAM_KEY_SEARCH,
PARAM_KEY_STATUS,
PARAM_KEY_RUNNER_TYPE,
PARAM_KEY_TAG,
PARAM_KEY_SEARCH,
PARAM_KEY_SORT,
PARAM_KEY_PAGE,
PARAM_KEY_AFTER,
Loading
Loading
@@ -40,7 +41,7 @@ export const fromUrlQueryToSearch = (query = window.location.search) => {
return {
filters: prepareTokens(
urlQueryToFilter(query, {
filterNamesAllowList: [PARAM_KEY_STATUS, PARAM_KEY_RUNNER_TYPE],
filterNamesAllowList: [PARAM_KEY_STATUS, PARAM_KEY_RUNNER_TYPE, PARAM_KEY_TAG],
filteredSearchTermKey: PARAM_KEY_SEARCH,
legacySpacesDecode: false,
}),
Loading
Loading
@@ -56,15 +57,19 @@ export const fromSearchToUrl = (
) => {
const filterParams = {
// Defaults
[PARAM_KEY_SEARCH]: null,
[PARAM_KEY_STATUS]: [],
[PARAM_KEY_RUNNER_TYPE]: [],
[PARAM_KEY_TAG]: [],
// Current filters
...filterToQueryObject(processFilters(filters), {
filteredSearchTermKey: PARAM_KEY_SEARCH,
}),
};
 
if (!filterParams[PARAM_KEY_SEARCH]) {
filterParams[PARAM_KEY_SEARCH] = null;
}
const isDefaultSort = sort !== DEFAULT_SORT;
const isFirstPage = pagination?.page === 1;
const otherParams = {
Loading
Loading
@@ -87,12 +92,12 @@ export const fromSearchToVariables = ({ filters = [], sort = null, pagination =
 
variables.search = queryObj[PARAM_KEY_SEARCH];
 
// TODO Get more than one value when GraphQL API supports OR for "status"
// TODO Get more than one value when GraphQL API supports OR for "status" or "runner_type"
[variables.status] = queryObj[PARAM_KEY_STATUS] || [];
// TODO Get more than one value when GraphQL API supports OR for "runner type"
[variables.type] = queryObj[PARAM_KEY_RUNNER_TYPE] || [];
 
variables.tagList = queryObj[PARAM_KEY_TAG];
if (sort) {
variables.sort = sort;
}
Loading
Loading
Loading
Loading
@@ -28141,6 +28141,9 @@ msgstr ""
msgid "Runners|Show Runner installation instructions"
msgstr ""
 
msgid "Runners|Something went wrong while fetching the tags suggestions"
msgstr ""
msgid "Runners|Tags"
msgstr ""
 
Loading
Loading
Loading
Loading
@@ -2,8 +2,10 @@ import { GlFilteredSearch, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import { PARAM_KEY_STATUS, PARAM_KEY_RUNNER_TYPE } from '~/runner/constants';
import TagToken from '~/runner/components/search_tokens/tag_token.vue';
import { PARAM_KEY_STATUS, PARAM_KEY_RUNNER_TYPE, PARAM_KEY_TAG } from '~/runner/constants';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
 
describe('RunnerList', () => {
let wrapper;
Loading
Loading
@@ -23,13 +25,13 @@ describe('RunnerList', () => {
wrapper = extendedWrapper(
shallowMount(RunnerFilteredSearchBar, {
propsData: {
namespace: 'runners',
value: {
filters: [],
sort: mockDefaultSort,
},
...props,
},
attrs: { namespace: 'runners' },
stubs: {
FilteredSearch,
GlFilteredSearch,
Loading
Loading
@@ -65,12 +67,18 @@ describe('RunnerList', () => {
expect(findFilteredSearch().props('tokens')).toEqual([
expect.objectContaining({
type: PARAM_KEY_STATUS,
token: BaseToken,
options: expect.any(Array),
}),
expect.objectContaining({
type: PARAM_KEY_RUNNER_TYPE,
token: BaseToken,
options: expect.any(Array),
}),
expect.objectContaining({
type: PARAM_KEY_TAG,
token: TagToken,
}),
]);
});
 
Loading
Loading
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerTag from '~/runner/components/runner_tag.vue';
describe('RunnerTag', () => {
let wrapper;
const findBadge = () => wrapper.findComponent(GlBadge);
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(RunnerTag, {
propsData: {
tag: 'tag1',
...props,
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('Displays tag text', () => {
expect(wrapper.text()).toBe('tag1');
});
it('Displays tags with correct style', () => {
expect(findBadge().props()).toMatchObject({
size: 'md',
variant: 'info',
});
});
it('Displays tags with small size', () => {
createComponent({
props: { size: 'sm' },
});
expect(findBadge().props('size')).toBe('sm');
});
});
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import RunnerTags from '~/runner/components/runner_tags.vue';
 
describe('RunnerTags', () => {
Loading
Loading
@@ -9,7 +9,7 @@ describe('RunnerTags', () => {
const findBadgesAt = (i = 0) => wrapper.findAllComponents(GlBadge).at(i);
 
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(RunnerTags, {
wrapper = mount(RunnerTags, {
propsData: {
tagList: ['tag1', 'tag2'],
...props,
Loading
Loading
@@ -45,14 +45,6 @@ describe('RunnerTags', () => {
expect(findBadge().props('size')).toBe('sm');
});
 
it('Displays tags with a variant', () => {
createComponent({
props: { variant: 'warning' },
});
expect(findBadge().props('variant')).toBe('warning');
});
it('Is empty when there are no tags', () => {
createComponent({
props: { tagList: null },
Loading
Loading
import { GlFilteredSearchSuggestion, GlLoadingIcon, GlToken } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import TagToken, { TAG_SUGGESTIONS_PATH } from '~/runner/components/search_tokens/tag_token.vue';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import { getRecentlyUsedSuggestions } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
jest.mock('~/flash');
jest.mock('~/vue_shared/components/filtered_search_bar/filtered_search_utils', () => ({
...jest.requireActual('~/vue_shared/components/filtered_search_bar/filtered_search_utils'),
getRecentlyUsedSuggestions: jest.fn(),
}));
const mockStorageKey = 'stored-recent-tags';
const mockTags = [
{ id: 1, name: 'linux' },
{ id: 2, name: 'windows' },
{ id: 3, name: 'mac' },
];
const mockTagsFiltered = [mockTags[0]];
const mockSearchTerm = mockTags[0].name;
const GlFilteredSearchTokenStub = {
template: `<div>
<slot name="view-token"></slot>
<slot name="suggestions"></slot>
</div>`,
};
const mockTagTokenConfig = {
icon: 'tag',
title: 'Tags',
type: 'tag',
token: TagToken,
recentTokenValuesStorageKey: mockStorageKey,
operators: OPERATOR_IS_ONLY,
};
describe('TagToken', () => {
let mock;
let wrapper;
const createComponent = (props = {}) => {
wrapper = mount(TagToken, {
propsData: {
config: mockTagTokenConfig,
value: { data: '' },
active: false,
...props,
},
provide: {
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
filteredSearchSuggestionListInstance: {
register: jest.fn(),
unregister: jest.fn(),
},
},
stubs: {
GlFilteredSearchToken: GlFilteredSearchTokenStub,
},
});
};
const findGlFilteredSearchSuggestions = () =>
wrapper.findAllComponents(GlFilteredSearchSuggestion);
const findGlFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchTokenStub);
const findToken = () => wrapper.findComponent(GlToken);
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
beforeEach(async () => {
mock = new MockAdapter(axios);
mock.onGet(TAG_SUGGESTIONS_PATH, { params: { search: '' } }).reply(200, mockTags);
mock
.onGet(TAG_SUGGESTIONS_PATH, { params: { search: mockSearchTerm } })
.reply(200, mockTagsFiltered);
getRecentlyUsedSuggestions.mockReturnValue([]);
createComponent();
await waitForPromises();
});
afterEach(() => {
getRecentlyUsedSuggestions.mockReset();
wrapper.destroy();
});
describe('when the tags token is displayed', () => {
it('requests tags suggestions', () => {
expect(mock.history.get[0].params).toEqual({ search: '' });
});
it('displays tags suggestions', () => {
mockTags.forEach(({ name }, i) => {
expect(findGlFilteredSearchSuggestions().at(i).text()).toBe(name);
});
});
});
describe('when suggestions are stored', () => {
const storedSuggestions = [{ id: 4, value: 'docker', text: 'docker' }];
beforeEach(async () => {
getRecentlyUsedSuggestions.mockReturnValue(storedSuggestions);
createComponent();
await waitForPromises();
});
it('suggestions are loaded from a correct key', () => {
expect(getRecentlyUsedSuggestions).toHaveBeenCalledWith(mockStorageKey);
});
it('displays stored tags suggestions', () => {
expect(findGlFilteredSearchSuggestions()).toHaveLength(
mockTags.length + storedSuggestions.length,
);
expect(findGlFilteredSearchSuggestions().at(0).text()).toBe(storedSuggestions[0].text);
});
});
describe('when the users filters suggestions', () => {
beforeEach(async () => {
findGlFilteredSearchToken().vm.$emit('input', { data: mockSearchTerm });
jest.runAllTimers();
});
it('requests filtered tags suggestions', async () => {
await waitForPromises();
expect(mock.history.get[1].params).toEqual({ search: mockSearchTerm });
});
it('shows the loading icon', async () => {
await nextTick();
expect(findGlLoadingIcon().exists()).toBe(true);
});
it('displays filtered tags suggestions', async () => {
await waitForPromises();
expect(findGlFilteredSearchSuggestions()).toHaveLength(mockTagsFiltered.length);
expect(findGlFilteredSearchSuggestions().at(0).text()).toBe(mockTagsFiltered[0].name);
});
});
describe('when suggestions cannot be loaded', () => {
beforeEach(async () => {
mock.onGet(TAG_SUGGESTIONS_PATH, { params: { search: '' } }).reply(500);
createComponent();
await waitForPromises();
});
it('error is shown', async () => {
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith({ message: expect.any(String) });
});
});
describe('when the user selects a value', () => {
beforeEach(async () => {
createComponent({ value: { data: mockTags[0].name } });
findGlFilteredSearchToken().vm.$emit('select');
await waitForPromises();
});
it('selected tag is displayed', async () => {
expect(findToken().exists()).toBe(true);
});
});
});
Loading
Loading
@@ -64,7 +64,7 @@ describe('RunnerListApp', () => {
};
 
const setQuery = (query) => {
window.location.href = `${TEST_HOST}/admin/runners/${query}`;
window.location.href = `${TEST_HOST}/admin/runners?${query}`;
window.location.search = query;
};
 
Loading
Loading
@@ -119,7 +119,7 @@ describe('RunnerListApp', () => {
 
describe('when a filter is preselected', () => {
beforeEach(async () => {
window.location.search = `?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}`;
setQuery(`?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}&tag[]=tag1`);
 
createComponentWithApollo();
await waitForPromises();
Loading
Loading
@@ -130,6 +130,7 @@ describe('RunnerListApp', () => {
filters: [
{ type: 'status', value: { data: STATUS_ACTIVE, operator: '=' } },
{ type: 'runner_type', value: { data: INSTANCE_TYPE, operator: '=' } },
{ type: 'tag', value: { data: 'tag1', operator: '=' } },
],
sort: 'CREATED_DESC',
pagination: { page: 1 },
Loading
Loading
@@ -140,6 +141,7 @@ describe('RunnerListApp', () => {
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
status: STATUS_ACTIVE,
type: INSTANCE_TYPE,
tagList: ['tag1'],
sort: DEFAULT_SORT,
first: RUNNER_PAGE_SIZE,
});
Loading
Loading
@@ -157,7 +159,7 @@ describe('RunnerListApp', () => {
it('updates the browser url', () => {
expect(updateHistory).toHaveBeenLastCalledWith({
title: expect.any(String),
url: 'http://test.host/admin/runners/?status[]=ACTIVE&sort=CREATED_ASC',
url: 'http://test.host/admin/runners?status[]=ACTIVE&sort=CREATED_ASC',
});
});
 
Loading
Loading
Loading
Loading
@@ -98,6 +98,37 @@ describe('search_params.js', () => {
first: RUNNER_PAGE_SIZE,
},
},
{
name: 'a tag',
urlQuery: '?tag[]=tag-1',
search: {
filters: [{ type: 'tag', value: { data: 'tag-1', operator: '=' } }],
pagination: { page: 1 },
sort: 'CREATED_DESC',
},
graphqlVariables: {
tagList: ['tag-1'],
first: 20,
sort: 'CREATED_DESC',
},
},
{
name: 'two tags',
urlQuery: '?tag[]=tag-1&tag[]=tag-2',
search: {
filters: [
{ type: 'tag', value: { data: 'tag-1', operator: '=' } },
{ type: 'tag', value: { data: 'tag-2', operator: '=' } },
],
pagination: { page: 1 },
sort: 'CREATED_DESC',
},
graphqlVariables: {
tagList: ['tag-1', 'tag-2'],
first: 20,
sort: 'CREATED_DESC',
},
},
{
name: 'the next page',
urlQuery: '?page=2&after=AFTER_CURSOR',
Loading
Loading
@@ -115,14 +146,15 @@ describe('search_params.js', () => {
graphqlVariables: { sort: 'CREATED_DESC', before: 'BEFORE_CURSOR', last: RUNNER_PAGE_SIZE },
},
{
name:
'the next page filtered by multiple status, a single instance type and a non default sort',
name: 'the next page filtered by a status, an instance type, tags and a non default sort',
urlQuery:
'?status[]=ACTIVE&runner_type[]=INSTANCE_TYPE&sort=CREATED_ASC&page=2&after=AFTER_CURSOR',
'?status[]=ACTIVE&runner_type[]=INSTANCE_TYPE&tag[]=tag-1&tag[]=tag-2&sort=CREATED_ASC&page=2&after=AFTER_CURSOR',
search: {
filters: [
{ type: 'status', value: { data: 'ACTIVE', operator: '=' } },
{ type: 'runner_type', value: { data: 'INSTANCE_TYPE', operator: '=' } },
{ type: 'tag', value: { data: 'tag-1', operator: '=' } },
{ type: 'tag', value: { data: 'tag-2', operator: '=' } },
],
pagination: { page: 2, after: 'AFTER_CURSOR' },
sort: 'CREATED_ASC',
Loading
Loading
@@ -130,6 +162,7 @@ describe('search_params.js', () => {
graphqlVariables: {
status: 'ACTIVE',
type: 'INSTANCE_TYPE',
tagList: ['tag-1', 'tag-2'],
sort: 'CREATED_ASC',
after: 'AFTER_CURSOR',
first: RUNNER_PAGE_SIZE,
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment