pipelines_table.vue 7.47 KB
Newer Older
1
<script>
2
import { GlButton, GlEmptyState, GlLoadingIcon, GlModal, GlLink } from '@gitlab/ui';
3
import { getParameterByName } from '~/lib/utils/common_utils';
4
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
5
import eventHub from '~/pipelines/event_hub';
6
import PipelinesMixin from '~/pipelines/mixins/pipelines_mixin';
7 8 9
import PipelinesService from '~/pipelines/services/pipelines_service';
import PipelineStore from '~/pipelines/stores/pipelines_store';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
10
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
11

12
export default {
13
  components: {
14
    GlButton,
15
    GlEmptyState,
16
    GlLink,
17
    GlLoadingIcon,
18
    GlModal,
19 20
    PipelinesTableComponent,
    TablePagination,
21
  },
22
  mixins: [PipelinesMixin, glFeatureFlagMixin()],
23 24 25 26
  props: {
    endpoint: {
      type: String,
      required: true,
27
    },
28 29 30 31 32 33 34 35 36
    errorStateSvgPath: {
      type: String,
      required: true,
    },
    viewType: {
      type: String,
      required: false,
      default: 'child',
    },
37
    canCreatePipelineInTargetProject: {
38 39 40 41
      type: Boolean,
      required: false,
      default: false,
    },
42 43 44 45 46 47 48 49 50 51
    sourceProjectFullPath: {
      type: String,
      required: false,
      default: '',
    },
    targetProjectFullPath: {
      type: String,
      required: false,
      default: '',
    },
52 53 54 55 56 57 58 59 60 61
    projectId: {
      type: String,
      required: false,
      default: '',
    },
    mergeRequestId: {
      type: Number,
      required: false,
      default: 0,
    },
62
  },
63

64 65
  data() {
    const store = new PipelineStore();
66

67 68 69
    return {
      store,
      state: store.state,
70 71
      page: getParameterByName('page') || '1',
      requestData: {},
72
      modalId: 'create-pipeline-for-fork-merge-request-modal',
73 74
    };
  },
75

76 77 78
  computed: {
    shouldRenderTable() {
      return !this.isLoading && this.state.pipelines.length > 0 && !this.hasError;
79
    },
80 81
    shouldRenderErrorState() {
      return this.hasError && !this.isLoading;
82
    },
83
    /**
84
     * The "Run pipeline" button can only be rendered when:
85
     * - In MR view -  we use `canCreatePipelineInTargetProject` for that purpose
86 87 88 89 90
     * - If the latest pipeline has the `detached_merge_request_pipeline` flag
     *
     * @returns {Boolean}
     */
    canRenderPipelineButton() {
91 92 93 94 95 96 97 98
      return this.latestPipelineDetachedFlag;
    },
    isForkMergeRequest() {
      return this.sourceProjectFullPath !== this.targetProjectFullPath;
    },
    isLatestPipelineCreatedInTargetProject() {
      const latest = this.state.pipelines[0];

99
      return latest?.project?.full_path === `/${this.targetProjectFullPath}`;
100 101 102 103 104 105 106
    },
    shouldShowSecurityWarning() {
      return (
        this.canCreatePipelineInTargetProject &&
        this.isForkMergeRequest &&
        !this.isLatestPipelineCreatedInTargetProject
      );
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
    },
    /**
     * Checks if either `detached_merge_request_pipeline` or
     * `merge_request_pipeline` are tru in the first
     * object in the pipelines array.
     *
     * @returns {Boolean}
     */
    latestPipelineDetachedFlag() {
      const latest = this.state.pipelines[0];
      return (
        latest &&
        latest.flags &&
        (latest.flags.detached_merge_request_pipeline || latest.flags.merge_request_pipeline)
      );
    },
123 124 125
  },
  created() {
    this.service = new PipelinesService(this.endpoint);
126
    this.requestData = { page: this.page };
127 128 129 130 131
  },
  methods: {
    successCallback(resp) {
      // depending of the endpoint the response can either bring a `pipelines` key or not.
      const pipelines = resp.data.pipelines || resp.data;
132 133

      this.store.storePagination(resp.headers);
134
      this.setCommonData(pipelines);
135

136 137 138 139 140
      const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
        detail: {
          pipelines: resp.data,
        },
      });
141

142 143 144 145
      // notifiy to update the count in tabs
      if (this.$el.parentElement) {
        this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
      }
146
    },
147
    /**
148
     * When the user clicks on the "Run pipeline" button
149 150 151 152 153 154 155 156 157 158 159 160 161 162
     * we need to make a post request and
     * to update the table content once the request is finished.
     *
     * We are emitting an event through the eventHub using the old pattern
     * to make use of the code in mixins/pipelines.js that handles all the
     * table events
     *
     */
    onClickRunPipeline() {
      eventHub.$emit('runMergeRequestPipeline', {
        projectId: this.projectId,
        mergeRequestId: this.mergeRequestId,
      });
    },
163 164 165 166 167 168 169
    tryRunPipeline() {
      if (!this.shouldShowSecurityWarning) {
        this.onClickRunPipeline();
      } else {
        this.$refs.modal.show();
      }
    },
170 171
  },
};
172 173 174
</script>
<template>
  <div class="content-list pipelines">
Clement Ho's avatar
Clement Ho committed
175
    <gl-loading-icon
176
      v-if="isLoading"
177
      :label="s__('Pipelines|Loading pipelines')"
178
      size="lg"
179
      class="prepend-top-20"
180
    />
181

182
    <gl-empty-state
183 184
      v-else-if="shouldRenderErrorState"
      :svg-path="errorStateSvgPath"
185
      :title="
Mike Greiling's avatar
Mike Greiling committed
186
        s__(`Pipelines|There was an error fetching the pipelines.
187
        Try again in a few moments or contact your support team.`)
Mike Greiling's avatar
Mike Greiling committed
188
      "
189
    />
190

191
    <div v-else-if="shouldRenderTable">
192 193 194
      <gl-button
        v-if="canRenderPipelineButton"
        block
pburdette's avatar
pburdette committed
195
        class="gl-mt-3 gl-mb-3 gl-lg-display-none"
196
        variant="confirm"
197 198 199 200
        data-testid="run_pipeline_button_mobile"
        :loading="state.isRunningMergeRequestPipeline"
        @click="tryRunPipeline"
      >
201
        {{ s__('Pipeline|Run pipeline') }}
202
      </gl-button>
203

204 205 206
      <pipelines-table-component
        :pipelines="state.pipelines"
        :update-graph-dropdown="updateGraphDropdown"
207
        :view-type="viewType"
208 209 210 211
      >
        <template #table-header-actions>
          <div v-if="canRenderPipelineButton" class="gl-text-right">
            <gl-button
212
              variant="confirm"
213 214 215 216
              data-testid="run_pipeline_button"
              :loading="state.isRunningMergeRequestPipeline"
              @click="tryRunPipeline"
            >
217
              {{ s__('Pipeline|Run pipeline') }}
218 219 220 221
            </gl-button>
          </div>
        </template>
      </pipelines-table-component>
222
    </div>
223

224 225 226 227 228 229
    <gl-modal
      v-if="canRenderPipelineButton"
      :id="modalId"
      ref="modal"
      :modal-id="modalId"
      :title="s__('Pipelines|Are you sure you want to run this pipeline?')"
230
      :ok-title="s__('Pipeline|Run pipeline')"
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
      ok-variant="danger"
      @ok="onClickRunPipeline"
    >
      <p>
        {{
          s__(
            'Pipelines|This pipeline will run code originating from a forked project merge request. This means that the code can potentially have security considerations like exposing CI variables.',
          )
        }}
      </p>
      <p>
        {{
          s__(
            "Pipelines|It is recommended the code is reviewed thoroughly before running this pipeline with the parent project's CI resource.",
          )
        }}
      </p>
      <p>
        {{
          s__('Pipelines|If you are unsure, please ask a project maintainer to review it for you.')
        }}
      </p>
      <gl-link
254
        href="/help/ci/pipelines/merge_request_pipelines.html#run-pipelines-in-the-parent-project-for-merge-requests-from-a-forked-project"
255 256 257 258 259 260
        target="_blank"
      >
        {{ s__('Pipelines|More Information') }}
      </gl-link>
    </gl-modal>

261 262 263 264 265
    <table-pagination
      v-if="shouldRenderPagination"
      :change="onChangePage"
      :page-info="state.pageInfo"
    />
266 267
  </div>
</template>