Commit 04b77a6c authored by Enrique Alcántara's avatar Enrique Alcántara

Merge branch 'filter-from-url-query-params-pipeline' into 'master'

Filter pipelines based on url query params

See merge request gitlab-org/gitlab!32230
parents aa735684 1eda0077
......@@ -51,6 +51,7 @@ document.addEventListener(
ciLintPath: this.dataset.ciLintPath,
resetCachePath: this.dataset.resetCachePath,
projectId: this.dataset.projectId,
params: JSON.parse(this.dataset.params),
},
});
},
......
<script>
import { isEqual } from 'lodash';
import { isEqual, pickBy } from 'lodash';
import { __, sprintf, s__ } from '../../locale';
import createFlash from '../../flash';
import PipelinesService from '../services/pipelines_service';
......@@ -10,7 +10,7 @@ import NavigationControls from './nav_controls.vue';
import { getParameterByName } from '../../lib/utils/common_utils';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING } from '../constants';
import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, SUPPORTED_FILTER_PARAMETERS } from '../constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
......@@ -86,6 +86,10 @@ export default {
type: String,
required: true,
},
params: {
type: Object,
required: true,
},
},
data() {
return {
......@@ -220,10 +224,15 @@ export default {
canFilterPipelines() {
return this.glFeatures.filterPipelinesSearch;
},
validatedParams() {
return pickBy(this.params, (val, key) => SUPPORTED_FILTER_PARAMETERS.includes(key) && val);
},
},
created() {
this.service = new PipelinesService(this.endpoint);
this.requestData = { page: this.page, scope: this.scope };
Object.assign(this.requestData, this.validatedParams);
},
methods: {
successCallback(resp) {
......@@ -306,6 +315,7 @@ export default {
v-if="canFilterPipelines"
:pipelines="state.pipelines"
:project-id="projectId"
:params="validatedParams"
@filterPipelines="filterPipelines"
/>
......
......@@ -3,9 +3,7 @@ import { GlFilteredSearch } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import PipelineTriggerAuthorToken from './tokens/pipeline_trigger_author_token.vue';
import PipelineBranchNameToken from './tokens/pipeline_branch_name_token.vue';
import Api from '~/api';
import createFlash from '~/flash';
import { FETCH_AUTHOR_ERROR_MESSAGE, FETCH_BRANCH_ERROR_MESSAGE } from '../constants';
import { map } from 'lodash';
export default {
components: {
......@@ -20,12 +18,10 @@ export default {
type: String,
required: true,
},
},
data() {
return {
projectUsers: null,
projectBranches: null,
};
params: {
type: Object,
required: true,
},
},
computed: {
tokens() {
......@@ -37,7 +33,6 @@ export default {
unique: true,
token: PipelineTriggerAuthorToken,
operators: [{ value: '=', description: __('is'), default: 'true' }],
triggerAuthors: this.projectUsers,
projectId: this.projectId,
},
{
......@@ -47,30 +42,16 @@ export default {
unique: true,
token: PipelineBranchNameToken,
operators: [{ value: '=', description: __('is'), default: 'true' }],
branches: this.projectBranches,
projectId: this.projectId,
},
];
},
},
created() {
Api.projectUsers(this.projectId)
.then(users => {
this.projectUsers = users;
})
.catch(err => {
createFlash(FETCH_AUTHOR_ERROR_MESSAGE);
throw err;
});
Api.branches(this.projectId)
.then(({ data }) => {
this.projectBranches = data.map(branch => branch.name);
})
.catch(err => {
createFlash(FETCH_BRANCH_ERROR_MESSAGE);
throw err;
});
paramsValue() {
return map(this.params, (val, key) => ({
type: key,
value: { data: val, operator: '=' },
}));
},
},
methods: {
onSubmit(filters) {
......@@ -85,6 +66,7 @@ export default {
<gl-filtered-search
:placeholder="__('Filter pipelines')"
:available-tokens="tokens"
:value="paramsValue"
@submit="onSubmit"
/>
</div>
......
......@@ -23,15 +23,18 @@ export default {
},
data() {
return {
branches: this.config.branches,
branches: null,
loading: true,
};
},
created() {
this.fetchBranches();
},
methods: {
fetchBranchBySearchTerm(searchTerm) {
Api.branches(this.config.projectId, searchTerm)
.then(res => {
this.branches = res.data.map(branch => branch.name);
fetchBranches(searchterm) {
Api.branches(this.config.projectId, searchterm)
.then(({ data }) => {
this.branches = data.map(branch => branch.name);
this.loading = false;
})
.catch(err => {
......@@ -41,7 +44,7 @@ export default {
});
},
searchBranches: debounce(function debounceSearch({ data }) {
this.fetchBranchBySearchTerm(data);
this.fetchBranches(data);
}, FILTER_PIPELINES_SEARCH_DELAY),
},
};
......
......@@ -36,7 +36,7 @@ export default {
},
data() {
return {
users: this.config.triggerAuthors,
users: [],
loading: true,
};
},
......@@ -50,11 +50,14 @@ export default {
});
},
},
created() {
this.fetchProjectUsers();
},
methods: {
fetchAuthorBySearchTerm(searchTerm) {
fetchProjectUsers(searchTerm) {
Api.projectUsers(this.config.projectId, searchTerm)
.then(res => {
this.users = res;
.then(users => {
this.users = users;
this.loading = false;
})
.catch(err => {
......@@ -64,7 +67,7 @@ export default {
});
},
searchAuthors: debounce(function debounceSearch({ data }) {
this.fetchAuthorBySearchTerm(data);
this.fetchProjectUsers(data);
}, FILTER_PIPELINES_SEARCH_DELAY),
},
};
......
......@@ -5,6 +5,7 @@ export const PIPELINES_TABLE = 'PIPELINES_TABLE';
export const LAYOUT_CHANGE_DELAY = 300;
export const FILTER_PIPELINES_SEARCH_DELAY = 200;
export const ANY_TRIGGER_AUTHOR = 'Any';
export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref'];
export const TestStatus = {
FAILED: 'failed',
......
......@@ -4,6 +4,7 @@
#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
project_id: @project.id,
params: params.to_json,
"help-page-path" => help_page_path('ci/quick_start/README'),
"help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
"empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
......
---
title: Filter pipelines based on url query params
merge_request: 32230
author:
type: added
......@@ -3,13 +3,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import PipelinesFilteredSearch from '~/pipelines/components/pipelines_filtered_search.vue';
import {
users,
mockSearch,
pipelineWithStages,
branches,
mockBranchesAfterMap,
} from '../mock_data';
import { users, mockSearch, pipelineWithStages, branches } from '../mock_data';
import { GlFilteredSearch } from '@gitlab/ui';
describe('Pipelines filtered search', () => {
......@@ -22,11 +16,12 @@ describe('Pipelines filtered search', () => {
.props('availableTokens')
.find(token => token.type === type);
const createComponent = () => {
const createComponent = (params = {}) => {
wrapper = mount(PipelinesFilteredSearch, {
propsData: {
pipelines: [pipelineWithStages],
projectId: '21',
params,
},
attachToDocument: true,
});
......@@ -60,7 +55,6 @@ describe('Pipelines filtered search', () => {
icon: 'user',
title: 'Trigger author',
unique: true,
triggerAuthors: users,
projectId: '21',
operators: [expect.objectContaining({ value: '=' })],
});
......@@ -70,28 +64,49 @@ describe('Pipelines filtered search', () => {
icon: 'branch',
title: 'Branch name',
unique: true,
branches: mockBranchesAfterMap,
projectId: '21',
operators: [expect.objectContaining({ value: '=' })],
});
});
it('fetches and sets project users', () => {
expect(Api.projectUsers).toHaveBeenCalled();
expect(wrapper.vm.projectUsers).toEqual(users);
});
it('fetches and sets branches', () => {
expect(Api.branches).toHaveBeenCalled();
expect(wrapper.vm.projectBranches).toEqual(mockBranchesAfterMap);
});
it('emits filterPipelines on submit with correct filter', () => {
findFilteredSearch().vm.$emit('submit', mockSearch);
expect(wrapper.emitted('filterPipelines')).toBeTruthy();
expect(wrapper.emitted('filterPipelines')[0]).toEqual([mockSearch]);
});
describe('Url query params', () => {
const params = {
username: 'deja.green',
ref: 'master',
};
beforeEach(() => {
createComponent(params);
});
it('sets default value if url query params', () => {
const expectedValueProp = [
{
type: 'username',
value: {
data: params.username,
operator: '=',
},
},
{
type: 'ref',
value: {
data: params.ref,
operator: '=',
},
},
{ type: 'filtered-search-term', value: { data: '' } },
];
expect(findFilteredSearch().props('value')).toEqual(expectedValueProp);
expect(findFilteredSearch().props('value')).toHaveLength(expectedValueProp.length);
});
});
});
......@@ -56,6 +56,7 @@ describe('Pipelines', () => {
propsData: {
store: new Store(),
projectId: '21',
params: {},
...props,
},
methods: {
......
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import PipelineBranchNameToken from '~/pipelines/components/tokens/pipeline_branch_name_token.vue';
import { branches } from '../mock_data';
import { branches, mockBranchesAfterMap } from '../mock_data';
describe('Pipeline Branch Name Token', () => {
let wrapper;
......@@ -46,6 +47,8 @@ describe('Pipeline Branch Name Token', () => {
};
beforeEach(() => {
jest.spyOn(Api, 'branches').mockResolvedValue({ data: branches });
createComponent();
});
......@@ -58,6 +61,13 @@ describe('Pipeline Branch Name Token', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
it('fetches and sets project branches', () => {
expect(Api.branches).toHaveBeenCalled();
expect(wrapper.vm.branches).toEqual(mockBranchesAfterMap);
expect(findLoadingIcon().exists()).toBe(false);
});
describe('displays loading icon correctly', () => {
it('shows loading icon', () => {
createComponent({ stubs }, { loading: true });
......
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import PipelineTriggerAuthorToken from '~/pipelines/components/tokens/pipeline_trigger_author_token.vue';
......@@ -45,6 +46,8 @@ describe('Pipeline Trigger Author Token', () => {
};
beforeEach(() => {
jest.spyOn(Api, 'projectUsers').mockResolvedValue(users);
createComponent();
});
......@@ -57,6 +60,13 @@ describe('Pipeline Trigger Author Token', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
it('fetches and sets project users', () => {
expect(Api.projectUsers).toHaveBeenCalled();
expect(wrapper.vm.users).toEqual(users);
expect(findLoadingIcon().exists()).toBe(false);
});
describe('displays loading icon correctly', () => {
it('shows loading icon', () => {
createComponent({ stubs }, { loading: true });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment