diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-07 12:10:26 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-07 12:10:26 +0300 |
commit | f4c6fbb86fbec3e5917e317b3490232d98531881 (patch) | |
tree | a2648b816d6be98456303f4059e342fe850c6c7e /app/assets | |
parent | 362b615a84bf303d5b5b1c3168d6592fb4306d9d (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
17 files changed, 231 insertions, 39 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index e67c56bc825..5d69340c9a8 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -1,4 +1,4 @@ -import { deprecatedCreateFlash as flash } from '~/flash'; +import createFlash from '~/flash'; import { __ } from '~/locale'; import axios from './lib/utils/axios_utils'; import { joinPaths } from './lib/utils/url_utility'; @@ -454,7 +454,9 @@ const Api = { }) .then(({ data }) => (callback ? callback(data) : data)) .catch(() => { - flash(__('Something went wrong while fetching projects')); + createFlash({ + message: __('Something went wrong while fetching projects'), + }); if (callback) { callback(); } @@ -642,7 +644,11 @@ const Api = { params: { ...defaults, ...options }, }) .then(({ data }) => callback(data)) - .catch(() => flash(__('Something went wrong while fetching projects'))); + .catch(() => + createFlash({ + message: __('Something went wrong while fetching projects'), + }), + ); }, branches(id, query = '', options = {}) { diff --git a/app/assets/javascripts/api/markdown_api.js b/app/assets/javascripts/api/markdown_api.js new file mode 100644 index 00000000000..5c9c1713bd8 --- /dev/null +++ b/app/assets/javascripts/api/markdown_api.js @@ -0,0 +1,11 @@ +import axios from '../lib/utils/axios_utils'; +import { buildApiUrl } from './api_utils'; + +const MARKDOWN_PATH = '/api/:version/markdown'; + +export function getMarkdown(options) { + const url = buildApiUrl(MARKDOWN_PATH); + return axios.post(url, { + ...options, + }); +} diff --git a/app/assets/javascripts/cycle_analytics/components/base.vue b/app/assets/javascripts/cycle_analytics/components/base.vue index e3703fc066d..8c1fecac3fc 100644 --- a/app/assets/javascripts/cycle_analytics/components/base.vue +++ b/app/assets/javascripts/cycle_analytics/components/base.vue @@ -54,6 +54,7 @@ export default { 'isEmptyStage', 'selectedStage', 'selectedStageEvents', + 'selectedStageError', 'stages', 'summary', 'startDate', @@ -72,6 +73,14 @@ export default { selectedStageReady() { return !this.isLoadingStage && this.selectedStage; }, + emptyStageTitle() { + return this.selectedStageError + ? this.selectedStageError + : __("We don't have enough data to show this stage."); + }, + emptyStageText() { + return !this.selectedStageError ? this.selectedStage.emptyStageText : ''; + }, }, methods: { ...mapActions([ @@ -206,9 +215,9 @@ export default { <gl-empty-state v-if="displayNotEnoughData" class="js-empty-state" - :description="selectedStage.emptyStageText" + :description="emptyStageText" :svg-path="noDataSvgPath" - :title="__('We don\'t have enough data to show this stage.')" + :title="emptyStageTitle" /> <component :is="selectedStage.component" diff --git a/app/assets/javascripts/cycle_analytics/store/actions.js b/app/assets/javascripts/cycle_analytics/store/actions.js index fe3c6d6b3ba..40e1c01f78b 100644 --- a/app/assets/javascripts/cycle_analytics/store/actions.js +++ b/app/assets/javascripts/cycle_analytics/store/actions.js @@ -33,7 +33,14 @@ export const fetchStageData = ({ state: { requestPath, selectedStage, startDate .get(`${requestPath}/events/${selectedStage.name}.json`, { params: { 'cycle_analytics[start_date]': startDate }, }) - .then(({ data }) => commit(types.RECEIVE_STAGE_DATA_SUCCESS, data)) + .then(({ data }) => { + // when there's a query timeout, the request succeeds but the error is encoded in the response data + if (data?.error) { + commit(types.RECEIVE_STAGE_DATA_ERROR, data.error); + } else { + commit(types.RECEIVE_STAGE_DATA_SUCCESS, data); + } + }) .catch(() => commit(types.RECEIVE_STAGE_DATA_ERROR)); }; diff --git a/app/assets/javascripts/cycle_analytics/store/mutations.js b/app/assets/javascripts/cycle_analytics/store/mutations.js index d5038630503..4d999b056b7 100644 --- a/app/assets/javascripts/cycle_analytics/store/mutations.js +++ b/app/assets/javascripts/cycle_analytics/store/mutations.js @@ -44,10 +44,11 @@ export default { state.selectedStageEvents = decorateEvents(events, selectedStage); state.hasError = false; }, - [types.RECEIVE_STAGE_DATA_ERROR](state) { + [types.RECEIVE_STAGE_DATA_ERROR](state, error) { state.isLoadingStage = false; state.isEmptyStage = true; state.selectedStageEvents = []; state.hasError = true; + state.selectedStageError = error; }, }; diff --git a/app/assets/javascripts/cycle_analytics/store/state.js b/app/assets/javascripts/cycle_analytics/store/state.js index 5db4e1878a9..b488340943a 100644 --- a/app/assets/javascripts/cycle_analytics/store/state.js +++ b/app/assets/javascripts/cycle_analytics/store/state.js @@ -9,6 +9,7 @@ export default () => ({ stats: [], selectedStage: {}, selectedStageEvents: [], + selectedStageError: '', medians: {}, hasError: false, isLoading: false, diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 0a038febb9f..b0bd95264b1 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -884,21 +884,6 @@ export const approximateDuration = (seconds = 0) => { }; /** - * A utility function which helps creating a date object - * for a specific date. Accepts the year, month and day - * returning a date object for the given params. - * - * @param {Int} year the full year as a number i.e. 2020 - * @param {Int} month the month index i.e. January => 0 - * @param {Int} day the day as a number i.e. 23 - * - * @return {Date} the date object from the params - */ -export const dateFromParams = (year, month, day) => { - return new Date(year, month, day); -}; - -/** * A utility function which computes the difference in seconds * between 2 dates. * diff --git a/app/assets/javascripts/repository/components/blob_header_edit.vue b/app/assets/javascripts/repository/components/blob_header_edit.vue index 49128096d42..3d97ebe89e4 100644 --- a/app/assets/javascripts/repository/components/blob_header_edit.vue +++ b/app/assets/javascripts/repository/components/blob_header_edit.vue @@ -1,6 +1,8 @@ <script> import { GlButton } from '@gitlab/ui'; import { __ } from '~/locale'; +import WebIdeLink from '~/vue_shared/components/web_ide_link.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { i18n: { @@ -9,7 +11,9 @@ export default { }, components: { GlButton, + WebIdeLink, }, + mixins: [glFeatureFlagsMixin()], props: { editPath: { type: String, @@ -24,7 +28,14 @@ export default { </script> <template> - <div> + <web-ide-link + v-if="glFeatures.consolidatedEditButton" + class="gl-mr-3" + :edit-url="editPath" + :web-ide-url="webIdePath" + :is-blob="true" + /> + <div v-else> <gl-button class="gl-mr-2" category="primary" variant="confirm" :href="editPath"> {{ $options.i18n.edit }} </gl-button> diff --git a/app/assets/javascripts/rest_api.js b/app/assets/javascripts/rest_api.js index ea8f87001f0..3e9e3e6f265 100644 --- a/app/assets/javascripts/rest_api.js +++ b/app/assets/javascripts/rest_api.js @@ -1,6 +1,7 @@ export * from './api/groups_api'; export * from './api/projects_api'; export * from './api/user_api'; +export * from './api/markdown_api'; // Note: It's not possible to spy on methods imported from this file in // Jest tests. See https://stackoverflow.com/a/53307822/1063392. diff --git a/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue new file mode 100644 index 00000000000..1ed07c39fd5 --- /dev/null +++ b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue @@ -0,0 +1,121 @@ +<script> +import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { __ } from '~/locale'; +import updateRunnerMutation from '~/runner/graphql/update_runner.mutation.graphql'; + +const i18n = { + I18N_EDIT: __('Edit'), + I18N_PAUSE: __('Pause'), + I18N_RESUME: __('Resume'), +}; + +export default { + components: { + GlButton, + GlButtonGroup, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + runner: { + type: Object, + required: true, + }, + }, + data() { + return { + updating: false, + }; + }, + computed: { + runnerNumericalId() { + return getIdFromGraphQLId(this.runner.id); + }, + runnerUrl() { + // TODO implement using webUrl from the API + return `${gon.gitlab_url || ''}/admin/runners/${this.runnerNumericalId}`; + }, + isActive() { + return this.runner.active; + }, + toggleActiveIcon() { + return this.isActive ? 'pause' : 'play'; + }, + toggleActiveTitle() { + if (this.updating) { + // Prevent a "sticky" tooltip: If this button is disabled, + // mouseout listeners will not run and the tooltip will + // stay stuck on the button. + return ''; + } + return this.isActive ? i18n.I18N_PAUSE : i18n.I18N_RESUME; + }, + }, + methods: { + async onToggleActive() { + this.updating = true; + // TODO In HAML iteration we had a confirmation modal via: + // data-confirm="_('Are you sure?')" + // this may not have to ported, this is an easily reversible operation + + try { + const toggledActive = !this.runner.active; + + const { + data: { + runnerUpdate: { errors }, + }, + } = await this.$apollo.mutate({ + mutation: updateRunnerMutation, + variables: { + input: { + id: this.runner.id, + active: toggledActive, + }, + }, + }); + + if (errors && errors.length) { + this.onError(new Error(errors[0])); + } + } catch (e) { + this.onError(e); + } finally { + this.updating = false; + } + }, + + onError(error) { + // TODO Render errors when "delete" action is done + // `active` toggle would not fail due to user input. + throw error; + }, + }, + i18n, +}; +</script> + +<template> + <gl-button-group> + <gl-button + v-gl-tooltip.hover.viewport + :title="$options.i18n.I18N_EDIT" + :aria-label="$options.i18n.I18N_EDIT" + icon="pencil" + :href="runnerUrl" + data-testid="edit-runner" + /> + <gl-button + v-gl-tooltip.hover.viewport + :title="toggleActiveTitle" + :aria-label="toggleActiveTitle" + :icon="toggleActiveIcon" + :loading="updating" + data-testid="toggle-active-runner" + @click="onToggleActive" + /> + <!-- TODO add delete action to update runners --> + </gl-button-group> +</template> diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue index 39d354db46d..41adbbb55f6 100644 --- a/app/assets/javascripts/runner/components/runner_list.vue +++ b/app/assets/javascripts/runner/components/runner_list.vue @@ -3,6 +3,7 @@ import { GlTable, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { formatNumber, sprintf, __, s__ } from '~/locale'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import RunnerActionsCell from './cells/runner_actions_cell.vue'; import RunnerNameCell from './cells/runner_name_cell.vue'; import RunnerTypeCell from './cells/runner_type_cell.vue'; import RunnerTags from './runner_tags.vue'; @@ -32,6 +33,7 @@ export default { GlTable, GlSkeletonLoader, TimeAgo, + RunnerActionsCell, RunnerNameCell, RunnerTags, RunnerTypeCell, @@ -132,8 +134,8 @@ export default { <template v-else>{{ __('Never') }}</template> </template> - <template #cell(actions)> - <!-- TODO add actions to update runners --> + <template #cell(actions)="{ item }"> + <runner-actions-cell :runner="item" /> </template> </gl-table> </div> diff --git a/app/assets/javascripts/runner/graphql/get_runners.query.graphql b/app/assets/javascripts/runner/graphql/get_runners.query.graphql index 3864dd5bf37..84b7e2547f5 100644 --- a/app/assets/javascripts/runner/graphql/get_runners.query.graphql +++ b/app/assets/javascripts/runner/graphql/get_runners.query.graphql @@ -1,3 +1,4 @@ +#import "~/runner/graphql/runner_node.fragment.graphql" #import "~/graphql_shared/fragments/pageInfo.fragment.graphql" query getRunners( @@ -19,17 +20,7 @@ query getRunners( sort: $sort ) { nodes { - id - description - runnerType - shortSha - version - revision - ipAddress - active - locked - tagList - contactedAt + ...RunnerNode } pageInfo { ...PageInfo diff --git a/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql b/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql new file mode 100644 index 00000000000..0835e3c7c09 --- /dev/null +++ b/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql @@ -0,0 +1,13 @@ +fragment RunnerNode on CiRunner { + id + description + runnerType + shortSha + version + revision + ipAddress + active + locked + tagList + contactedAt +} diff --git a/app/assets/javascripts/runner/graphql/update_runner.mutation.graphql b/app/assets/javascripts/runner/graphql/update_runner.mutation.graphql new file mode 100644 index 00000000000..d17147b871b --- /dev/null +++ b/app/assets/javascripts/runner/graphql/update_runner.mutation.graphql @@ -0,0 +1,10 @@ +#import "~/runner/graphql/runner_node.fragment.graphql" + +mutation runnerUpdate($input: RunnerUpdateInput!) { + runnerUpdate(input: $input) { + runner { + ...RunnerNode + } + errors + } +} diff --git a/app/assets/javascripts/runner/runner_list/runner_list_app.vue b/app/assets/javascripts/runner/runner_list/runner_list_app.vue index 93d1cf38b9b..d53774709ba 100644 --- a/app/assets/javascripts/runner/runner_list/runner_list_app.vue +++ b/app/assets/javascripts/runner/runner_list/runner_list_app.vue @@ -1,5 +1,6 @@ <script> import * as Sentry from '@sentry/browser'; +import { fetchPolicies } from '~/lib/graphql'; import { updateHistory } from '~/lib/utils/url_utility'; import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerList from '../components/runner_list.vue'; @@ -43,6 +44,10 @@ export default { apollo: { runners: { query: getRunnersQuery, + // Runners can be updated by users directly in this list. + // A "cache and network" policy prevents outdated filtered + // results. + fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, variables() { return this.variables; }, diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js index 0af679644f3..0c3f273fec7 100644 --- a/app/assets/javascripts/search/store/actions.js +++ b/app/assets/javascripts/search/store/actions.js @@ -29,6 +29,7 @@ export const fetchProjects = ({ commit, state }, search) => { }; if (groupId) { + // TODO (https://gitlab.com/gitlab-org/gitlab/-/issues/323331): For errors `createFlash` is called twice; in `callback` and in `Api.groupProjects` Api.groupProjects(groupId, search, {}, callback); } else { // The .catch() is due to the API method not handling a rejection properly diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index a666b11ba62..07de525b1fa 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -22,7 +22,13 @@ import { __ } from '~/locale'; import SmartInterval from '~/smart_interval'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import MergeRequest from '../../../merge_request'; -import { AUTO_MERGE_STRATEGIES, DANGER, CONFIRM, WARNING } from '../../constants'; +import { + AUTO_MERGE_STRATEGIES, + DANGER, + CONFIRM, + WARNING, + MT_MERGE_STRATEGY, +} from '../../constants'; import eventHub from '../../event_hub'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import MergeRequestStore from '../../stores/mr_widget_store'; @@ -223,7 +229,7 @@ export default { return PIPELINE_SUCCESS_STATE; }, mergeButtonVariant() { - if (this.status === PIPELINE_FAILED_STATE) { + if (this.status === PIPELINE_FAILED_STATE || this.isPipelineFailed) { return DANGER; } @@ -286,6 +292,9 @@ export default { shaMismatchLink() { return this.mr.mergeRequestDiffsPath; }, + showDangerMessageForMergeTrain() { + return this.preferredAutoMergeStrategy === MT_MERGE_STRATEGY && this.isPipelineFailed; + }, }, mounted() { if (this.glFeatures.mergeRequestWidgetGraphql) { @@ -499,7 +508,7 @@ export default { v-if="shouldShowMergeImmediatelyDropdown" v-gl-tooltip.hover.focus="__('Select merge moment')" :disabled="isMergeButtonDisabled" - variant="info" + :variant="mergeButtonVariant" data-qa-selector="merge_moment_dropdown" toggle-class="btn-icon js-merge-moment" > @@ -579,6 +588,14 @@ export default { </gl-sprintf> </span> </div> + + <div + v-if="showDangerMessageForMergeTrain" + class="gl-mt-5 gl-text-gray-500" + data-testid="failed-pipeline-merge-train-text" + > + {{ __('The latest pipeline for this merge request did not complete successfully.') }} + </div> </div> </div> <merge-train-helper-text |