Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue')
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue362
1 files changed, 362 insertions, 0 deletions
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
new file mode 100644
index 00000000000..0c531650fd2
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -0,0 +1,362 @@
+<script>
+import { isEqual } from 'lodash';
+import { __, s__ } from '~/locale';
+import createFlash from '~/flash';
+import PipelinesService from '../../services/pipelines_service';
+import pipelinesMixin from '../../mixins/pipelines';
+import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
+import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
+import NavigationControls from './nav_controls.vue';
+import { getParameterByName } from '~/lib/utils/common_utils';
+import CIPaginationMixin from '~/vue_shared/mixins/ci_pagination_api_mixin';
+import Icon from '~/vue_shared/components/icon.vue';
+import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
+import { validateParams } from '../../utils';
+import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../../constants';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+
+export default {
+ components: {
+ TablePagination,
+ NavigationTabs,
+ NavigationControls,
+ PipelinesFilteredSearch,
+ Icon,
+ },
+ mixins: [pipelinesMixin, CIPaginationMixin, glFeatureFlagsMixin()],
+ props: {
+ store: {
+ type: Object,
+ required: true,
+ },
+ // Can be rendered in 3 different places, with some visual differences
+ // Accepts root | child
+ // `root` -> main view
+ // `child` -> rendered inside MR or Commit View
+ viewType: {
+ type: String,
+ required: false,
+ default: 'root',
+ },
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ pipelineScheduleUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ emptyStateSvgPath: {
+ type: String,
+ required: true,
+ },
+ errorStateSvgPath: {
+ type: String,
+ required: true,
+ },
+ noPipelinesSvgPath: {
+ type: String,
+ required: true,
+ },
+ autoDevopsPath: {
+ type: String,
+ required: true,
+ },
+ hasGitlabCi: {
+ type: Boolean,
+ required: true,
+ },
+ canCreatePipeline: {
+ type: Boolean,
+ required: true,
+ },
+ ciLintPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ resetCachePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ newPipelinePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ projectId: {
+ type: String,
+ required: true,
+ },
+ params: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ // Start with loading state to avoid a glitch when the empty state will be rendered
+ isLoading: true,
+ state: this.store.state,
+ scope: getParameterByName('scope') || 'all',
+ page: getParameterByName('page') || '1',
+ requestData: {},
+ isResetCacheButtonLoading: false,
+ };
+ },
+ stateMap: {
+ // with tabs
+ loading: 'loading',
+ tableList: 'tableList',
+ error: 'error',
+ emptyTab: 'emptyTab',
+
+ // without tabs
+ emptyState: 'emptyState',
+ },
+ scopes: {
+ all: 'all',
+ finished: 'finished',
+ branches: 'branches',
+ tags: 'tags',
+ },
+ computed: {
+ /**
+ * `hasGitlabCi` handles both internal and external CI.
+ * The order on which the checks are made in this method is
+ * important to guarantee we handle all the corner cases.
+ */
+ stateToRender() {
+ const { stateMap } = this.$options;
+
+ if (this.isLoading) {
+ return stateMap.loading;
+ }
+
+ if (this.hasError) {
+ return stateMap.error;
+ }
+
+ if (this.state.pipelines.length) {
+ return stateMap.tableList;
+ }
+
+ if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) {
+ return stateMap.emptyTab;
+ }
+
+ return stateMap.emptyState;
+ },
+ /**
+ * Tabs are rendered in all states except empty state.
+ * They are not rendered before the first request to avoid a flicker on first load.
+ */
+ shouldRenderTabs() {
+ const { stateMap } = this.$options;
+ return (
+ this.hasMadeRequest &&
+ [stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes(
+ this.stateToRender,
+ )
+ );
+ },
+
+ shouldRenderButtons() {
+ return (
+ (this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs
+ );
+ },
+
+ emptyTabMessage() {
+ if (this.scope === this.$options.scopes.finished) {
+ return s__('Pipelines|There are currently no finished pipelines.');
+ }
+
+ return s__('Pipelines|There are currently no pipelines.');
+ },
+
+ tabs() {
+ const { count } = this.state;
+ const { scopes } = this.$options;
+
+ return [
+ {
+ name: __('All'),
+ scope: scopes.all,
+ count: count.all,
+ isActive: this.scope === 'all',
+ },
+ {
+ name: __('Finished'),
+ scope: scopes.finished,
+ isActive: this.scope === 'finished',
+ },
+ {
+ name: __('Branches'),
+ scope: scopes.branches,
+ isActive: this.scope === 'branches',
+ },
+ {
+ name: __('Tags'),
+ scope: scopes.tags,
+ isActive: this.scope === 'tags',
+ },
+ ];
+ },
+ canFilterPipelines() {
+ return this.glFeatures.filterPipelinesSearch;
+ },
+ validatedParams() {
+ return validateParams(this.params);
+ },
+ },
+ created() {
+ this.service = new PipelinesService(this.endpoint);
+ this.requestData = { page: this.page, scope: this.scope, ...this.validatedParams };
+ },
+ methods: {
+ successCallback(resp) {
+ // Because we are polling & the user is interacting verify if the response received
+ // matches the last request made
+ if (isEqual(resp.config.params, this.requestData)) {
+ this.store.storeCount(resp.data.count);
+ this.store.storePagination(resp.headers);
+ this.setCommonData(resp.data.pipelines);
+ }
+ },
+ handleResetRunnersCache(endpoint) {
+ this.isResetCacheButtonLoading = true;
+
+ this.service
+ .postAction(endpoint)
+ .then(() => {
+ this.isResetCacheButtonLoading = false;
+ createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
+ })
+ .catch(() => {
+ this.isResetCacheButtonLoading = false;
+ createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
+ });
+ },
+ resetRequestData() {
+ this.requestData = { page: this.page, scope: this.scope };
+ },
+ filterPipelines(filters) {
+ this.resetRequestData();
+
+ filters.forEach(filter => {
+ // do not add Any for username query param, so we
+ // can fetch all trigger authors
+ if (
+ filter.type &&
+ filter.value.data !== ANY_TRIGGER_AUTHOR &&
+ filter.type !== FILTER_TAG_IDENTIFIER
+ ) {
+ this.requestData[filter.type] = filter.value.data;
+ }
+
+ if (filter.type === FILTER_TAG_IDENTIFIER) {
+ this.requestData.ref = filter.value.data;
+ }
+
+ if (!filter.type) {
+ createFlash(RAW_TEXT_WARNING, 'warning');
+ }
+ });
+
+ if (filters.length === 0) {
+ this.resetRequestData();
+ }
+
+ this.updateContent(this.requestData);
+ },
+ },
+};
+</script>
+<template>
+ <div class="pipelines-container">
+ <div
+ v-if="shouldRenderTabs || shouldRenderButtons"
+ class="top-area scrolling-tabs-container inner-page-scroll-tabs"
+ >
+ <div class="fade-left"><icon name="chevron-lg-left" :size="12" /></div>
+ <div class="fade-right"><icon name="chevron-lg-right" :size="12" /></div>
+
+ <navigation-tabs
+ v-if="shouldRenderTabs"
+ :tabs="tabs"
+ scope="pipelines"
+ @onChangeTab="onChangeTab"
+ />
+
+ <navigation-controls
+ v-if="shouldRenderButtons"
+ :new-pipeline-path="newPipelinePath"
+ :reset-cache-path="resetCachePath"
+ :ci-lint-path="ciLintPath"
+ :is-reset-cache-button-loading="isResetCacheButtonLoading"
+ @resetRunnersCache="handleResetRunnersCache"
+ />
+ </div>
+
+ <pipelines-filtered-search
+ v-if="canFilterPipelines"
+ :project-id="projectId"
+ :params="validatedParams"
+ @filterPipelines="filterPipelines"
+ />
+
+ <div class="content-list pipelines">
+ <gl-loading-icon
+ v-if="stateToRender === $options.stateMap.loading"
+ :label="s__('Pipelines|Loading Pipelines')"
+ size="lg"
+ class="prepend-top-20"
+ />
+
+ <empty-state
+ v-else-if="stateToRender === $options.stateMap.emptyState"
+ :help-page-path="helpPagePath"
+ :empty-state-svg-path="emptyStateSvgPath"
+ :can-set-ci="canCreatePipeline"
+ />
+
+ <svg-blank-state
+ v-else-if="stateToRender === $options.stateMap.error"
+ :svg-path="errorStateSvgPath"
+ :message="
+ s__(`Pipelines|There was an error fetching the pipelines.
+ Try again in a few moments or contact your support team.`)
+ "
+ />
+
+ <svg-blank-state
+ v-else-if="stateToRender === $options.stateMap.emptyTab"
+ :svg-path="noPipelinesSvgPath"
+ :message="emptyTabMessage"
+ />
+
+ <div v-else-if="stateToRender === $options.stateMap.tableList" class="table-holder">
+ <pipelines-table-component
+ :pipelines="state.pipelines"
+ :pipeline-schedule-url="pipelineScheduleUrl"
+ :update-graph-dropdown="updateGraphDropdown"
+ :auto-devops-help-path="autoDevopsPath"
+ :view-type="viewType"
+ />
+ </div>
+
+ <table-pagination
+ v-if="shouldRenderPagination"
+ :change="onChangePage"
+ :page-info="state.pageInfo"
+ />
+ </div>
+ </div>
+</template>