job_app.vue 7.75 KB
Newer Older
1
<script>
2 3
import _ from 'underscore';
import { mapGetters, mapState, mapActions } from 'vuex';
Clement Ho's avatar
Clement Ho committed
4
import { GlLoadingIcon } from '@gitlab/ui';
5
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
6
import { polyfillSticky } from '~/lib/utils/sticky';
7 8 9
import bp from '~/breakpoints';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue';
10
import Icon from '~/vue_shared/components/icon.vue';
11 12 13 14 15 16 17 18
import createStore from '../store';
import EmptyState from './empty_state.vue';
import EnvironmentsBlock from './environments_block.vue';
import ErasedBlock from './erased_block.vue';
import Log from './job_log.vue';
import LogTopBar from './job_log_controllers.vue';
import StuckBlock from './stuck_block.vue';
import Sidebar from './sidebar.vue';
19 20
import { sprintf } from '~/locale';
import delayedJobMixin from '../mixins/delayed_job_mixin';
21

22 23 24 25 26 27 28 29 30
export default {
  name: 'JobPageApp',
  store: createStore(),
  components: {
    CiHeader,
    Callout,
    EmptyState,
    EnvironmentsBlock,
    ErasedBlock,
31
    Icon,
32 33 34 35
    Log,
    LogTopBar,
    StuckBlock,
    Sidebar,
36
    GlLoadingIcon,
37
  },
38
  mixins: [delayedJobMixin],
39 40 41 42 43
  props: {
    runnerSettingsUrl: {
      type: String,
      required: false,
      default: null,
44
    },
45 46 47 48
    runnerHelpUrl: {
      type: String,
      required: false,
      default: null,
49
    },
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    endpoint: {
      type: String,
      required: true,
    },
    terminalPath: {
      type: String,
      required: false,
      default: null,
    },
    pagePath: {
      type: String,
      required: true,
    },
    logState: {
      type: String,
      required: true,
    },
  },
  computed: {
    ...mapState([
      'isLoading',
      'job',
      'isSidebarOpen',
      'trace',
      'isTraceComplete',
      'traceSize',
      'isTraceSizeVisible',
      'isScrollBottomDisabled',
      'isScrollTopDisabled',
      'isScrolledToBottomBeforeReceivingTrace',
      'hasError',
    ]),
    ...mapGetters([
      'headerTime',
      'shouldRenderCalloutMessage',
      'shouldRenderTriggeredLabel',
      'hasEnvironment',
      'hasTrace',
      'emptyStateIllustration',
      'isScrollingDown',
      'emptyStateAction',
      'hasRunnersForProject',
    ]),
93

94 95
    shouldRenderContent() {
      return !this.isLoading && !this.hasError;
96
    },
97 98 99 100 101 102 103 104 105 106 107

    emptyStateTitle() {
      const { emptyStateIllustration, remainingTime } = this;
      const { title } = emptyStateIllustration;

      if (this.isDelayedJob) {
        return sprintf(title, { remainingTime });
      }

      return title;
    },
108 109 110 111 112 113 114 115
  },
  watch: {
    // Once the job log is loaded,
    // fetch the stages for the dropdown on the sidebar
    job(newVal, oldVal) {
      if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) {
        this.fetchStages();
      }
116 117 118 119 120 121 122 123

      if (newVal.archived) {
        this.$nextTick(() => {
          if (this.$refs.sticky) {
            polyfillSticky(this.$refs.sticky);
          }
        });
      }
124
    },
125 126 127
  },
  created() {
    this.throttled = _.throttle(this.toggleScrollButtons, 100);
128

129 130 131 132 133
    this.setJobEndpoint(this.endpoint);
    this.setTraceOptions({
      logState: this.logState,
      pagePath: this.pagePath,
    });
134

135 136
    this.fetchJob();
    this.fetchTrace();
137

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
    window.addEventListener('resize', this.onResize);
    window.addEventListener('scroll', this.updateScroll);
  },
  mounted() {
    this.updateSidebar();
  },
  destroyed() {
    window.removeEventListener('resize', this.onResize);
    window.removeEventListener('scroll', this.updateScroll);
  },
  methods: {
    ...mapActions([
      'setJobEndpoint',
      'setTraceOptions',
      'fetchJob',
      'fetchStages',
      'hideSidebar',
      'showSidebar',
      'toggleSidebar',
      'fetchTrace',
      'scrollBottom',
      'scrollTop',
      'toggleScrollButtons',
      'toggleScrollAnimation',
    ]),
    onResize() {
164
      this.updateSidebar();
165
      this.updateScroll();
166
    },
167 168 169 170 171 172
    updateSidebar() {
      if (bp.getBreakpointSize() === 'xs') {
        this.hideSidebar();
      } else if (!this.isSidebarOpen) {
        this.showSidebar();
      }
173
    },
174 175 176 177 178 179
    updateScroll() {
      if (!isScrolledToBottom()) {
        this.toggleScrollAnimation(false);
      } else if (this.isScrollingDown) {
        this.toggleScrollAnimation(true);
      }
180

181
      this.throttled();
182
    },
183 184
  },
};
185 186 187 188 189 190
</script>
<template>
  <div>
    <gl-loading-icon
      v-if="isLoading"
      :size="2"
191
      class="js-job-loading qa-loading-animation prepend-top-20"
192 193
    />

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
    <template v-else-if="shouldRenderContent">
      <div class="js-job-content build-page">
        <!-- Header Section -->
        <header>
          <div class="js-build-header build-header top-area">
            <ci-header
              :status="job.status"
              :item-id="job.id"
              :time="headerTime"
              :user="job.user"
              :has-sidebar-button="true"
              :should-render-triggered-label="shouldRenderTriggeredLabel"
              :item-name="__('Job')"
              @clickedSidebarButton="toggleSidebar"
            />
          </div>

Mike Greiling's avatar
Mike Greiling committed
211
          <callout v-if="shouldRenderCalloutMessage" :message="job.callout_message" />
212 213 214 215 216
        </header>
        <!-- EO Header Section -->

        <!-- Body Section -->
        <stuck-block
217
          v-if="job.stuck"
218
          class="js-job-stuck"
219
          :has-no-runners-for-project="hasRunnersForProject"
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
          :tags="job.tags"
          :runners-path="runnerSettingsUrl"
        />

        <environments-block
          v-if="hasEnvironment"
          class="js-job-environment"
          :deployment-status="job.deployment_status"
          :icon-status="job.status"
        />

        <erased-block
          v-if="job.erased_at"
          class="js-job-erased-block"
          :user="job.erased_by"
          :erased-at="job.erased_at"
        />

238
        <div
239 240
          v-if="job.archived"
          ref="sticky"
241 242
          class="js-archived-job prepend-top-default archived-job"
          :class="{ 'sticky-top border-bottom-0': hasTrace }"
243
        >
Mike Greiling's avatar
Mike Greiling committed
244
          <icon name="lock" class="align-text-bottom" />
245

246 247
          {{ __('This job is archived. Only the complete pipeline can be retried.') }}
        </div>
Mike Greiling's avatar
Mike Greiling committed
248
        <!-- job log -->
249 250 251 252 253
        <div
          v-if="hasTrace"
          class="build-trace-container"
          :class="{ 'prepend-top-default': !job.archived }"
        >
254 255 256
          <log-top-bar
            :class="{
              'sidebar-expanded': isSidebarOpen,
257
              'sidebar-collapsed': !isSidebarOpen,
Mike Greiling's avatar
Mike Greiling committed
258
              'has-archived-block': job.archived,
259 260 261 262 263 264 265 266 267 268 269
            }"
            :erase-path="job.erase_path"
            :size="traceSize"
            :raw-path="job.raw_path"
            :is-scroll-bottom-disabled="isScrollBottomDisabled"
            :is-scroll-top-disabled="isScrollTopDisabled"
            :is-trace-size-visible="isTraceSizeVisible"
            :is-scrolling-down="isScrollingDown"
            @scrollJobLogTop="scrollTop"
            @scrollJobLogBottom="scrollBottom"
          />
Mike Greiling's avatar
Mike Greiling committed
270
          <log :trace="trace" :is-complete="isTraceComplete" />
271
        </div>
272
        <!-- EO job log -->
273

Mike Greiling's avatar
Mike Greiling committed
274
        <!-- empty state -->
275 276 277 278 279
        <empty-state
          v-if="!hasTrace"
          class="js-job-empty-state"
          :illustration-path="emptyStateIllustration.image"
          :illustration-size-class="emptyStateIllustration.size"
280
          :title="emptyStateTitle"
281 282
          :content="emptyStateIllustration.content"
          :action="emptyStateAction"
283
        />
Mike Greiling's avatar
Mike Greiling committed
284
        <!-- EO empty state -->
285

Mike Greiling's avatar
Mike Greiling committed
286
        <!-- EO Body Section -->
287
      </div>
288
    </template>
289 290 291 292 293 294

    <sidebar
      v-if="shouldRenderContent"
      class="js-job-sidebar"
      :class="{
        'right-sidebar-expanded': isSidebarOpen,
Mike Greiling's avatar
Mike Greiling committed
295
        'right-sidebar-collapsed': !isSidebarOpen,
296 297 298
      }"
      :runner-help-url="runnerHelpUrl"
    />
299 300
  </div>
</template>