Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
362b615a84
commit
f4c6fbb86f
|
|
@ -149,16 +149,13 @@ setup-test-env:
|
|||
- .rails-job-base
|
||||
- .setup-test-env-cache
|
||||
- .rails:rules:code-backstage-qa
|
||||
- .use-pg12
|
||||
stage: prepare
|
||||
variables:
|
||||
GITLAB_TEST_EAGER_LOAD: "0"
|
||||
SETUP_DB: "false"
|
||||
script:
|
||||
- run_timed_command "scripts/setup-test-env"
|
||||
- echo -e "\e[0Ksection_start:`date +%s`:gitaly-test-build[collapsed=true]\r\e[0KCompiling Gitaly binaries"
|
||||
- run_timed_command "bundle exec ruby -I. -e 'require \"config/environment\"; TestEnv.init'"
|
||||
- run_timed_command "scripts/gitaly-test-build" # Do not use 'bundle exec' here
|
||||
- echo -e "\e[0Ksection_end:`date +%s`:gitaly-test-build\r\e[0K"
|
||||
|
||||
artifacts:
|
||||
expire_in: 7d
|
||||
paths:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
fcea557863c9433fe1ed9f0fd8d446c4a592891e
|
||||
8fd337f0f718f257ae72a66c464143a395af4c05
|
||||
|
|
|
|||
|
|
@ -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 = {}) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export default () => ({
|
|||
stats: [],
|
||||
selectedStage: {},
|
||||
selectedStageEvents: [],
|
||||
selectedStageError: '',
|
||||
medians: {},
|
||||
hasError: false,
|
||||
isLoading: false,
|
||||
|
|
|
|||
|
|
@ -883,21 +883,6 @@ export const approximateDuration = (seconds = 0) => {
|
|||
return n__('1 day', '%d days', seconds < ONE_DAY_LIMIT ? 1 : days);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
fragment RunnerNode on CiRunner {
|
||||
id
|
||||
description
|
||||
runnerType
|
||||
shortSha
|
||||
version
|
||||
revision
|
||||
ipAddress
|
||||
active
|
||||
locked
|
||||
tagList
|
||||
contactedAt
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
#import "~/runner/graphql/runner_node.fragment.graphql"
|
||||
|
||||
mutation runnerUpdate($input: RunnerUpdateInput!) {
|
||||
runnerUpdate(input: $input) {
|
||||
runner {
|
||||
...RunnerNode
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:consolidated_edit_button, @project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
def new
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ class ProtectedBranch < ApplicationRecord
|
|||
end
|
||||
|
||||
def self.allow_force_push?(project, ref_name)
|
||||
return false unless ::Feature.enabled?(:allow_force_push_to_protected_branches, project, default_enabled: :yaml)
|
||||
|
||||
project.protected_branches.allowing_force_push.matching(ref_name).any?
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,10 @@
|
|||
= s_("ProtectedBranch|Allowed to merge")
|
||||
%th
|
||||
= s_("ProtectedBranch|Allowed to push")
|
||||
|
||||
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project, default_enabled: :yaml)
|
||||
%th
|
||||
= s_("ProtectedBranch|Allow force push")
|
||||
%span.has-tooltip{ data: { container: 'body' }, title: s_('ProtectedBranch|Allow force push for all users with push access.'), 'aria-hidden': 'true' }
|
||||
= sprite_icon('question', size: 16, css_class: 'gl-text-gray-500')
|
||||
%th
|
||||
= s_("ProtectedBranch|Allow force push")
|
||||
%span.has-tooltip{ data: { container: 'body' }, title: s_('ProtectedBranch|Allow force push for all users with push access.'), 'aria-hidden': 'true' }
|
||||
= sprite_icon('question', size: 16, css_class: 'gl-text-gray-500')
|
||||
|
||||
= render_if_exists 'projects/protected_branches/ee/code_owner_approval_table_head'
|
||||
|
||||
|
|
|
|||
|
|
@ -21,13 +21,12 @@
|
|||
= f.label :push_access_levels_attributes, s_("ProtectedBranch|Allowed to push:"), class: 'col-md-2 text-left text-md-right'
|
||||
.col-md-10
|
||||
= yield :push_access_levels
|
||||
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project, default_enabled: :yaml)
|
||||
.form-group.row
|
||||
= f.label :allow_force_push, s_("ProtectedBranch|Allow force push:"), class: 'col-md-2 gl-text-left text-md-right'
|
||||
.col-md-10
|
||||
= render "shared/buttons/project_feature_toggle", class_list: "js-force-push-toggle project-feature-toggle"
|
||||
.form-text.gl-text-gray-600.gl-mt-0
|
||||
= s_("ProtectedBranch|Allow force push for all users with push access.")
|
||||
.form-group.row
|
||||
= f.label :allow_force_push, s_("ProtectedBranch|Allow force push:"), class: 'col-md-2 gl-text-left text-md-right'
|
||||
.col-md-10
|
||||
= render "shared/buttons/project_feature_toggle", class_list: "js-force-push-toggle project-feature-toggle"
|
||||
.form-text.gl-text-gray-600.gl-mt-0
|
||||
= s_("ProtectedBranch|Allow force push for all users with push access.")
|
||||
= render_if_exists 'projects/protected_branches/ee/code_owner_approval_form', f: f
|
||||
.card-footer
|
||||
= f.submit s_('ProtectedBranch|Protect'), class: 'gl-button btn btn-confirm', disabled: true, data: { qa_selector: 'protect_button' }
|
||||
|
|
|
|||
|
|
@ -33,6 +33,5 @@
|
|||
%p.small
|
||||
= _('Members of %{group} can also push to this branch: %{branch}') % { group: (group_push_access_levels.size > 1 ? 'these groups' : 'this group'), branch: group_push_access_levels.map(&:humanize).to_sentence }
|
||||
|
||||
- if ::Feature.enabled?(:allow_force_push_to_protected_branches, @project, default_enabled: :yaml)
|
||||
%td
|
||||
= render "shared/buttons/project_feature_toggle", is_checked: protected_branch.allow_force_push, label: s_("ProtectedBranch|Toggle allow force push"), class_list: "js-force-push-toggle project-feature-toggle", data: { qa_selector: 'force_push_toggle_button', qa_branch_name: protected_branch.name }
|
||||
%td
|
||||
= render "shared/buttons/project_feature_toggle", is_checked: protected_branch.allow_force_push, label: s_("ProtectedBranch|Toggle allow force push"), class_list: "js-force-push-toggle project-feature-toggle", data: { qa_selector: 'force_push_toggle_button', qa_branch_name: protected_branch.name }
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: allow_force_push_to_protected_branches
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55261
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323431
|
||||
milestone: '13.10'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: true
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: combined_menu
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56249
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321904
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/324086
|
||||
milestone: '13.10'
|
||||
type: development
|
||||
group: group::editor
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ GET /projects/:id/repository/commits
|
|||
| `with_stats` | boolean | no | Stats about each commit are added to the response |
|
||||
| `first_parent` | boolean | no | Follow only the first parent commit upon seeing a merge commit |
|
||||
| `order` | string | no | List commits in order. Possible values: `default`, [`topo`](https://git-scm.com/docs/git-log#Documentation/git-log.txt---topo-order). Defaults to `default`, the commits are shown in reverse chronological order. |
|
||||
| `trailers` | boolean | no | Parse and include [Git trailers](https://git-scm.com/docs/git-interpret-trailers) for every commit |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits"
|
||||
|
|
|
|||
|
|
@ -84,4 +84,4 @@ processes.
|
|||
For time periods in which no merge requests were deployed, the charts render a
|
||||
red, dashed line.
|
||||
|
||||
These charts are only available for projects.
|
||||
These charts are available for both groups and projects.
|
||||
|
|
|
|||
|
|
@ -179,9 +179,7 @@ command line or a Git client application.
|
|||
## Allow force push on protected branches
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15611) in GitLab 13.10 behind a disabled feature flag.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - It's recommended for production use.
|
||||
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-allow-force-push-on-protected-branches).
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/323431) in GitLab 14.0.
|
||||
|
||||
WARNING:
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
|
|
@ -249,25 +247,6 @@ run CI/CD pipelines and execute actions on jobs that are related to those branch
|
|||
See [Security on protected branches](../../ci/pipelines/index.md#pipeline-security-on-protected-branches)
|
||||
for details about the pipelines security model.
|
||||
|
||||
## Enable or disable allow force push on protected branches **(FREE SELF)**
|
||||
|
||||
Allow force push on protected branches is ready for
|
||||
production use. It is deployed behind a feature flag that is **enabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||
can enable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:allow_force_push_to_protected_branches)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:allow_force_push_to_protected_branches)
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
- **13.5**: [Allow Deploy keys to push to protected branches once more](https://gitlab.com/gitlab-org/gitlab/-/issues/30769).
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'mime/types'
|
||||
|
||||
module API
|
||||
|
|
@ -41,6 +40,7 @@ module API
|
|||
optional :with_stats, type: Boolean, desc: 'Stats about each commit will be added to the response'
|
||||
optional :first_parent, type: Boolean, desc: 'Only include the first parent of merges'
|
||||
optional :order, type: String, desc: 'List commits in order', default: 'default', values: %w[default topo]
|
||||
optional :trailers, type: Boolean, desc: 'Parse and include Git trailers for every commit', default: false
|
||||
use :pagination
|
||||
end
|
||||
get ':id/repository/commits' do
|
||||
|
|
@ -62,7 +62,8 @@ module API
|
|||
after: after,
|
||||
all: all,
|
||||
first_parent: first_parent,
|
||||
order: order)
|
||||
order: order,
|
||||
trailers: params[:trailers])
|
||||
|
||||
serializer = with_stats ? Entities::CommitWithStats : Entities::Commit
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module API
|
|||
expose :safe_message, as: :message
|
||||
expose :author_name, :author_email, :authored_date
|
||||
expose :committer_name, :committer_email, :committed_date
|
||||
expose :trailers
|
||||
|
||||
expose :web_url do |commit, _options|
|
||||
Gitlab::UrlBuilder.build(commit)
|
||||
|
|
|
|||
|
|
@ -24,26 +24,10 @@ module Gitlab::Ci
|
|||
@key_width = badge.customization.dig(:key_width)
|
||||
end
|
||||
|
||||
def key_text
|
||||
if @key_text && @key_text.size <= MAX_KEY_TEXT_SIZE
|
||||
@key_text
|
||||
else
|
||||
@entity.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def value_text
|
||||
@status ? ("%.2f%%" % @status) : 'unknown'
|
||||
end
|
||||
|
||||
def key_width
|
||||
if @key_width && @key_width.between?(1, MAX_KEY_WIDTH)
|
||||
@key_width
|
||||
else
|
||||
62
|
||||
end
|
||||
end
|
||||
|
||||
def value_width
|
||||
@status ? 54 : 58
|
||||
end
|
||||
|
|
|
|||
|
|
@ -28,26 +28,10 @@ module Gitlab::Ci
|
|||
@key_width = badge.customization.dig(:key_width)
|
||||
end
|
||||
|
||||
def key_text
|
||||
if @key_text && @key_text.size <= MAX_KEY_TEXT_SIZE
|
||||
@key_text
|
||||
else
|
||||
@entity.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def value_text
|
||||
STATUS_RENAME[@status.to_s] || @status.to_s
|
||||
end
|
||||
|
||||
def key_width
|
||||
if @key_width && @key_width.between?(1, MAX_KEY_WIDTH)
|
||||
@key_width
|
||||
else
|
||||
62
|
||||
end
|
||||
end
|
||||
|
||||
def value_width
|
||||
54
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ module Gitlab::Ci
|
|||
class Template
|
||||
MAX_KEY_TEXT_SIZE = 64
|
||||
MAX_KEY_WIDTH = 512
|
||||
DEFAULT_KEY_WIDTH = 62
|
||||
|
||||
def initialize(badge)
|
||||
@entity = badge.entity
|
||||
|
|
@ -15,7 +16,11 @@ module Gitlab::Ci
|
|||
end
|
||||
|
||||
def key_text
|
||||
raise NotImplementedError
|
||||
if @key_text && @key_text.size <= MAX_KEY_TEXT_SIZE
|
||||
@key_text
|
||||
else
|
||||
@entity.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def value_text
|
||||
|
|
@ -23,7 +28,11 @@ module Gitlab::Ci
|
|||
end
|
||||
|
||||
def key_width
|
||||
raise NotImplementedError
|
||||
if @key_width && @key_width.between?(1, MAX_KEY_WIDTH)
|
||||
@key_width
|
||||
else
|
||||
DEFAULT_KEY_WIDTH
|
||||
end
|
||||
end
|
||||
|
||||
def value_width
|
||||
|
|
|
|||
|
|
@ -32,10 +32,13 @@ module Gitlab
|
|||
|
||||
return true unless location
|
||||
|
||||
job_data_consistency = worker_class.get_data_consistency
|
||||
job[:data_consistency] = job_data_consistency.to_s
|
||||
|
||||
if replica_caught_up?(location)
|
||||
job[:database_chosen] = 'replica'
|
||||
false
|
||||
elsif worker_class.get_data_consistency == :delayed && not_yet_retried?(job)
|
||||
elsif job_data_consistency == :delayed && not_yet_retried?(job)
|
||||
job[:database_chosen] = 'retry'
|
||||
raise JobReplicaNotUpToDate, "Sidekiq job #{worker_class} JID-#{job['jid']} couldn't use the replica."\
|
||||
" Replica was not up to date."
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ module Gitlab
|
|||
raise "storage #{storage.inspect} is missing a gitaly_address"
|
||||
end
|
||||
|
||||
unless %w(tcp unix tls).include?(URI(address).scheme)
|
||||
unless URI(address).scheme.in?(%w(tcp unix tls))
|
||||
raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix' or 'tls'"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ module Gitlab
|
|||
@legacy_disk_path = File.expand_path(storage['path'], Rails.root) if storage['path']
|
||||
|
||||
storage['path'] = Deprecated
|
||||
@hash = ActiveSupport::HashWithIndifferentAccess.new(storage)
|
||||
@hash = storage.with_indifferent_access
|
||||
end
|
||||
|
||||
def gitaly_address
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
namespace :gitlab do
|
||||
namespace :gitaly do
|
||||
desc 'GitLab | Gitaly | Install or upgrade gitaly'
|
||||
task :install, [:dir, :storage_path, :repo] => :with_gitlab_helpers do |t, args|
|
||||
task :install, [:dir, :storage_path, :repo] => :gitlab_environment do |t, args|
|
||||
warn_user_is_not_gitlab
|
||||
|
||||
unless args.dir.present? && args.storage_path.present?
|
||||
|
|
|
|||
|
|
@ -6,7 +6,3 @@ StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON']
|
|||
task gitlab_environment: :environment do
|
||||
extend SystemCheck::Helpers
|
||||
end
|
||||
|
||||
task :with_gitlab_helpers do
|
||||
extend SystemCheck::Helpers
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5720,6 +5720,9 @@ msgstr ""
|
|||
msgid "CICDAnalytics|Deployment frequency"
|
||||
msgstr ""
|
||||
|
||||
msgid "CICDAnalytics|Lead time"
|
||||
msgstr ""
|
||||
|
||||
msgid "CICDAnalytics|Projects with releases"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32625,6 +32628,9 @@ msgstr ""
|
|||
msgid "The latest artifacts created by jobs in the most recent successful pipeline will be stored."
|
||||
msgstr ""
|
||||
|
||||
msgid "The latest pipeline for this merge request did not complete successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "The license key is invalid. Make sure it is exactly as you received it from GitLab Inc."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@
|
|||
"commander": "^2.18.0",
|
||||
"custom-jquery-matchers": "^2.1.0",
|
||||
"docdash": "^1.0.2",
|
||||
"eslint": "7.27.0",
|
||||
"eslint": "7.28.0",
|
||||
"eslint-import-resolver-jest": "3.0.0",
|
||||
"eslint-import-resolver-webpack": "0.13.1",
|
||||
"eslint-plugin-jasmine": "4.1.2",
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'bundler/setup'
|
||||
|
||||
require 'request_store'
|
||||
require 'rake'
|
||||
require 'active_support/dependencies'
|
||||
require 'active_support/dependencies/autoload'
|
||||
require 'active_support/core_ext/numeric'
|
||||
require 'active_support/string_inquirer'
|
||||
|
||||
module Rails
|
||||
extend self
|
||||
|
||||
def root
|
||||
Pathname.new(File.expand_path('..', __dir__))
|
||||
end
|
||||
|
||||
def env
|
||||
@_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test")
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport::Dependencies.autoload_paths << 'lib'
|
||||
|
||||
load File.expand_path('../lib/tasks/gitlab/helpers.rake', __dir__)
|
||||
load File.expand_path('../lib/tasks/gitlab/gitaly.rake', __dir__)
|
||||
|
||||
# Required for config/0_inject_enterprise_edition_module.rb, lib/gitlab/access.rb
|
||||
require_dependency File.expand_path('../lib/gitlab', __dir__)
|
||||
|
||||
require_dependency File.expand_path('../config/initializers/0_inject_enterprise_edition_module', __dir__)
|
||||
|
||||
# Require for lib/gitlab/gitaly_client/storage_settings.rb and config/initializers/1_settings.rb
|
||||
require 'active_support/hash_with_indifferent_access'
|
||||
|
||||
# Required for lib/gitlab/visibility_level.rb and lib/gitlab/safe_request_store.rb
|
||||
require 'active_support/concern'
|
||||
require 'active_support/core_ext/module/delegation'
|
||||
|
||||
# Required for lib/system_check/helpers.rb
|
||||
require_dependency File.expand_path('../lib/gitlab/task_helpers', __dir__)
|
||||
|
||||
# Required for lib/tasks/gitlab/helpers.rake
|
||||
require_dependency File.expand_path('../lib/system_check/helpers', __dir__)
|
||||
|
||||
# Required for config/initializers/1_settings.rb
|
||||
require 'omniauth'
|
||||
require 'omniauth-github'
|
||||
require 'etc'
|
||||
require_dependency File.expand_path('../lib/gitlab/access', __dir__)
|
||||
|
||||
require_dependency File.expand_path('../config/initializers/1_settings', __dir__)
|
||||
|
||||
Gitlab.ee do
|
||||
load File.expand_path('../ee/lib/tasks/gitlab/indexer.rake', __dir__)
|
||||
|
||||
require_dependency File.expand_path('../ee/lib/gitlab/elastic/indexer', __dir__)
|
||||
require_dependency File.expand_path('../lib/gitlab/utils/override', __dir__)
|
||||
end
|
||||
|
||||
require_dependency File.expand_path('../spec/support/helpers/test_env', __dir__)
|
||||
|
||||
TestEnv.init
|
||||
|
|
@ -5,7 +5,7 @@ import AxiosMockAdapter from 'axios-mock-adapter';
|
|||
import ProjectSelect from '~/boards/components/project_select_deprecated.vue';
|
||||
import { ListType } from '~/boards/constants';
|
||||
import eventHub from '~/boards/eventhub';
|
||||
import { deprecatedCreateFlash as flash } from '~/flash';
|
||||
import createFlash from '~/flash';
|
||||
import httpStatus from '~/lib/utils/http_status';
|
||||
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
|
||||
|
||||
|
|
@ -237,8 +237,10 @@ describe('ProjectSelect component', () => {
|
|||
|
||||
await searchForProject('foobar');
|
||||
|
||||
expect(flash).toHaveBeenCalledTimes(1);
|
||||
expect(flash).toHaveBeenCalledWith('Something went wrong while fetching projects');
|
||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: 'Something went wrong while fetching projects',
|
||||
});
|
||||
});
|
||||
|
||||
describe('with non-empty search result', () => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
exports[`Value stream analytics component isEmptyStage = true renders the empty stage with \`Not enough data\` message 1`] = `"<gl-empty-state-stub title=\\"We don't have enough data to show this stage.\\" svgpath=\\"path/to/no/data\\" description=\\"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.\\" class=\\"js-empty-state\\"></gl-empty-state-stub>"`;
|
||||
|
||||
exports[`Value stream analytics component isEmptyStage = true with a selectedStageError renders the empty stage with \`There is too much data to calculate\` message 1`] = `"<gl-empty-state-stub title=\\"There is too much data to calculate\\" svgpath=\\"path/to/no/data\\" description=\\"\\" class=\\"js-empty-state\\"></gl-empty-state-stub>"`;
|
||||
|
||||
exports[`Value stream analytics component isLoading = true renders the path navigation component with prop \`loading\` set to true 1`] = `"<path-navigation-stub loading=\\"true\\" stages=\\"\\" selectedstage=\\"[object Object]\\" class=\\"js-path-navigation gl-w-full gl-pb-2\\"></path-navigation-stub>"`;
|
||||
|
||||
exports[`Value stream analytics component without enough permissions renders the empty stage with \`You need permission\` message 1`] = `"<gl-empty-state-stub title=\\"You need permission.\\" svgpath=\\"path/to/no/access\\" description=\\"Want to see the data? Please ask an administrator for access.\\" class=\\"js-empty-state\\"></gl-empty-state-stub>"`;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ describe('Value stream analytics component', () => {
|
|||
isEmptyStage: false,
|
||||
selectedStageEvents,
|
||||
selectedStage,
|
||||
selectedStageError: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
@ -133,6 +134,22 @@ describe('Value stream analytics component', () => {
|
|||
it('renders the empty stage with `Not enough data` message', () => {
|
||||
expect(findEmptyStage().html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('with a selectedStageError', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent({
|
||||
initialState: {
|
||||
selectedStage,
|
||||
isEmptyStage: true,
|
||||
selectedStageError: 'There is too much data to calculate',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the empty stage with `There is too much data to calculate` message', () => {
|
||||
expect(findEmptyStage().html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without enough permissions', () => {
|
||||
|
|
|
|||
|
|
@ -106,6 +106,32 @@ describe('Project Value Stream Analytics actions', () => {
|
|||
expectedActions: [],
|
||||
}));
|
||||
|
||||
describe('with a successful request, but an error in the payload', () => {
|
||||
const tooMuchDataError = 'Too much data';
|
||||
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
requestPath: mockRequestPath,
|
||||
startDate: mockStartDate,
|
||||
selectedStage,
|
||||
};
|
||||
mock = new MockAdapter(axios);
|
||||
mock.onGet(mockStagePath).reply(httpStatusCodes.OK, { error: tooMuchDataError });
|
||||
});
|
||||
|
||||
it(`commits the 'RECEIVE_STAGE_DATA_ERROR' mutation`, () =>
|
||||
testAction({
|
||||
action: actions.fetchStageData,
|
||||
state,
|
||||
payload: { error: tooMuchDataError },
|
||||
expectedMutations: [
|
||||
{ type: 'REQUEST_STAGE_DATA' },
|
||||
{ type: 'RECEIVE_STAGE_DATA_ERROR', payload: tooMuchDataError },
|
||||
],
|
||||
expectedActions: [],
|
||||
}));
|
||||
});
|
||||
|
||||
describe('with a failing request', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
|
|
|
|||
|
|
@ -889,17 +889,6 @@ describe('localTimeAgo', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('dateFromParams', () => {
|
||||
it('returns the expected date object', () => {
|
||||
const expectedDate = new Date('2019-07-17T00:00:00.000Z');
|
||||
const date = datetimeUtility.dateFromParams(2019, 6, 17);
|
||||
|
||||
expect(date.getYear()).toBe(expectedDate.getYear());
|
||||
expect(date.getMonth()).toBe(expectedDate.getMonth());
|
||||
expect(date.getDate()).toBe(expectedDate.getDate());
|
||||
});
|
||||
});
|
||||
|
||||
describe('differenceInSeconds', () => {
|
||||
const startDateTime = new Date('2019-07-17T00:00:00.000Z');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import BlobHeaderEdit from '~/repository/components/blob_header_edit.vue';
|
||||
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
|
||||
|
||||
const DEFAULT_PROPS = {
|
||||
editPath: 'some_file.js/edit',
|
||||
|
|
@ -10,12 +11,17 @@ const DEFAULT_PROPS = {
|
|||
describe('BlobHeaderEdit component', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
const createComponent = (consolidatedEditButton = false, props = {}) => {
|
||||
wrapper = shallowMount(BlobHeaderEdit, {
|
||||
propsData: {
|
||||
...DEFAULT_PROPS,
|
||||
...props,
|
||||
},
|
||||
provide: {
|
||||
glFeatures: {
|
||||
consolidatedEditButton,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -27,6 +33,7 @@ describe('BlobHeaderEdit component', () => {
|
|||
const findButtons = () => wrapper.findAll(GlButton);
|
||||
const findEditButton = () => findButtons().at(0);
|
||||
const findWebIdeButton = () => findButtons().at(1);
|
||||
const findWebIdeLink = () => wrapper.find(WebIdeLink);
|
||||
|
||||
it('renders component', () => {
|
||||
createComponent();
|
||||
|
|
@ -60,4 +67,16 @@ describe('BlobHeaderEdit component', () => {
|
|||
expect(findWebIdeButton().text()).toBe('Web IDE');
|
||||
expect(findWebIdeButton()).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('renders WebIdeLink component', () => {
|
||||
createComponent(true);
|
||||
|
||||
const { editPath: editUrl, webIdePath: webIdeUrl } = DEFAULT_PROPS;
|
||||
|
||||
expect(findWebIdeLink().props()).toMatchObject({
|
||||
editUrl,
|
||||
webIdeUrl,
|
||||
isBlob: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue';
|
||||
import updateRunnerMutation from '~/runner/graphql/update_runner.mutation.graphql';
|
||||
|
||||
const mockId = '1';
|
||||
|
||||
describe('RunnerTypeCell', () => {
|
||||
let wrapper;
|
||||
let mutate;
|
||||
|
||||
const findEditBtn = () => wrapper.findByTestId('edit-runner');
|
||||
const findToggleActiveBtn = () => wrapper.findByTestId('toggle-active-runner');
|
||||
|
||||
const createComponent = ({ active = true } = {}, options) => {
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(RunnerActionCell, {
|
||||
propsData: {
|
||||
runner: {
|
||||
id: `gid://gitlab/Ci::Runner/${mockId}`,
|
||||
active,
|
||||
},
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
mutate,
|
||||
},
|
||||
},
|
||||
...options,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mutate = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mutate.mockReset();
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('Displays the runner edit link with the correct href', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findEditBtn().attributes('href')).toBe('/admin/runners/1');
|
||||
});
|
||||
|
||||
describe.each`
|
||||
state | label | icon | isActive | newActiveValue
|
||||
${'active'} | ${'Pause'} | ${'pause'} | ${true} | ${false}
|
||||
${'paused'} | ${'Resume'} | ${'play'} | ${false} | ${true}
|
||||
`('When the runner is $state', ({ label, icon, isActive, newActiveValue }) => {
|
||||
beforeEach(() => {
|
||||
mutate.mockResolvedValue({
|
||||
data: {
|
||||
runnerUpdate: {
|
||||
runner: {
|
||||
id: `gid://gitlab/Ci::Runner/1`,
|
||||
__typename: 'CiRunner',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
createComponent({ active: isActive });
|
||||
});
|
||||
|
||||
it(`Displays a ${icon} button`, () => {
|
||||
expect(findToggleActiveBtn().props('loading')).toBe(false);
|
||||
expect(findToggleActiveBtn().props('icon')).toBe(icon);
|
||||
expect(findToggleActiveBtn().attributes('title')).toBe(label);
|
||||
expect(findToggleActiveBtn().attributes('aria-label')).toBe(label);
|
||||
});
|
||||
|
||||
it(`After clicking the ${icon} button, the button has a loading state`, async () => {
|
||||
await findToggleActiveBtn().vm.$emit('click');
|
||||
|
||||
expect(findToggleActiveBtn().props('loading')).toBe(true);
|
||||
expect(findToggleActiveBtn().attributes('title')).toBe('');
|
||||
expect(findToggleActiveBtn().attributes('aria-label')).toBe('');
|
||||
});
|
||||
|
||||
describe(`When clicking on the ${icon} button`, () => {
|
||||
beforeEach(async () => {
|
||||
await findToggleActiveBtn().vm.$emit('click');
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it(`The apollo mutation to set active to ${newActiveValue} is called`, () => {
|
||||
expect(mutate).toHaveBeenCalledTimes(1);
|
||||
expect(mutate).toHaveBeenCalledWith({
|
||||
mutation: updateRunnerMutation,
|
||||
variables: {
|
||||
input: {
|
||||
id: `gid://gitlab/Ci::Runner/${mockId}`,
|
||||
active: newActiveValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('The button does not have a loading state', () => {
|
||||
expect(findToggleActiveBtn().props('loading')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -17,7 +17,7 @@ describe('RunnerList', () => {
|
|||
const findHeaders = () => wrapper.findAll('th');
|
||||
const findRows = () => wrapper.findAll('[data-testid^="runner-row-"]');
|
||||
const findCell = ({ row = 0, fieldKey }) =>
|
||||
findRows().at(row).find(`[data-testid="td-${fieldKey}"]`);
|
||||
extendedWrapper(findRows().at(row).find(`[data-testid="td-${fieldKey}"]`));
|
||||
|
||||
const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => {
|
||||
wrapper = extendedWrapper(
|
||||
|
|
@ -93,7 +93,12 @@ describe('RunnerList', () => {
|
|||
expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('');
|
||||
expect(findCell({ fieldKey: 'tagList' }).text()).toBe('');
|
||||
expect(findCell({ fieldKey: 'contactedAt' }).text()).toEqual(expect.any(String));
|
||||
expect(findCell({ fieldKey: 'actions' }).text()).toBe('');
|
||||
|
||||
// Actions
|
||||
const actions = findCell({ fieldKey: 'actions' });
|
||||
|
||||
expect(actions.findByTestId('edit-runner').exists()).toBe(true);
|
||||
expect(actions.findByTestId('toggle-active-runner').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('Links to the runner page', () => {
|
||||
|
|
|
|||
|
|
@ -20,9 +20,8 @@ describe('Global Search Store Actions', () => {
|
|||
let mock;
|
||||
let state;
|
||||
|
||||
const noCallback = () => {};
|
||||
const flashCallback = () => {
|
||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
||||
const flashCallback = (callCount) => {
|
||||
expect(createFlash).toHaveBeenCalledTimes(callCount);
|
||||
createFlash.mockClear();
|
||||
};
|
||||
|
||||
|
|
@ -37,19 +36,21 @@ describe('Global Search Store Actions', () => {
|
|||
});
|
||||
|
||||
describe.each`
|
||||
action | axiosMock | type | expectedMutations | callback
|
||||
${actions.fetchGroups} | ${{ method: 'onGet', code: 200, res: MOCK_GROUPS }} | ${'success'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_SUCCESS, payload: MOCK_GROUPS }]} | ${noCallback}
|
||||
${actions.fetchGroups} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_ERROR }]} | ${flashCallback}
|
||||
${actions.fetchProjects} | ${{ method: 'onGet', code: 200, res: MOCK_PROJECTS }} | ${'success'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_SUCCESS, payload: MOCK_PROJECTS }]} | ${noCallback}
|
||||
${actions.fetchProjects} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_ERROR }]} | ${flashCallback}
|
||||
`(`axios calls`, ({ action, axiosMock, type, expectedMutations, callback }) => {
|
||||
action | axiosMock | type | expectedMutations | flashCallCount
|
||||
${actions.fetchGroups} | ${{ method: 'onGet', code: 200, res: MOCK_GROUPS }} | ${'success'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_SUCCESS, payload: MOCK_GROUPS }]} | ${0}
|
||||
${actions.fetchGroups} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_ERROR }]} | ${1}
|
||||
${actions.fetchProjects} | ${{ method: 'onGet', code: 200, res: MOCK_PROJECTS }} | ${'success'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_SUCCESS, payload: MOCK_PROJECTS }]} | ${0}
|
||||
${actions.fetchProjects} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_ERROR }]} | ${2}
|
||||
`(`axios calls`, ({ action, axiosMock, type, expectedMutations, flashCallCount }) => {
|
||||
describe(action.name, () => {
|
||||
describe(`on ${type}`, () => {
|
||||
beforeEach(() => {
|
||||
mock[axiosMock.method]().replyOnce(axiosMock.code, axiosMock.res);
|
||||
});
|
||||
it(`should dispatch the correct mutations`, () => {
|
||||
return testAction({ action, state, expectedMutations }).then(() => callback());
|
||||
return testAction({ action, state, expectedMutations }).then(() =>
|
||||
flashCallback(flashCallCount),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,31 +6,7 @@ RSpec.describe Gitlab::Ci::Badge::Coverage::Template do
|
|||
let(:badge) { double(entity: 'coverage', status: 90.00, customization: {}) }
|
||||
let(:template) { described_class.new(badge) }
|
||||
|
||||
describe '#key_text' do
|
||||
it 'says coverage by default' do
|
||||
expect(template.key_text).to eq 'coverage'
|
||||
end
|
||||
|
||||
context 'when custom key_text is defined' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_text: "custom text" })
|
||||
end
|
||||
|
||||
it 'returns custom value' do
|
||||
expect(template.key_text).to eq "custom text"
|
||||
end
|
||||
|
||||
context 'when its size is larger than the max allowed value' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_text: 't' * 65 })
|
||||
end
|
||||
|
||||
it 'returns default value' do
|
||||
expect(template.key_text).to eq 'coverage'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
it_behaves_like 'a badge template', 'coverage'
|
||||
|
||||
describe '#value_text' do
|
||||
context 'when coverage is known' do
|
||||
|
|
@ -60,32 +36,6 @@ RSpec.describe Gitlab::Ci::Badge::Coverage::Template do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#key_width' do
|
||||
it 'is fixed by default' do
|
||||
expect(template.key_width).to eq 62
|
||||
end
|
||||
|
||||
context 'when custom key_width is defined' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_width: 101 })
|
||||
end
|
||||
|
||||
it 'returns custom value' do
|
||||
expect(template.key_width).to eq 101
|
||||
end
|
||||
|
||||
context 'when it is larger than the max allowed value' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_width: 513 })
|
||||
end
|
||||
|
||||
it 'returns default value' do
|
||||
expect(template.key_width).to eq 62
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#value_width' do
|
||||
context 'when coverage is known' do
|
||||
it 'is narrower when coverage is known' do
|
||||
|
|
|
|||
|
|
@ -6,31 +6,7 @@ RSpec.describe Gitlab::Ci::Badge::Pipeline::Template do
|
|||
let(:badge) { double(entity: 'pipeline', status: 'success', customization: {}) }
|
||||
let(:template) { described_class.new(badge) }
|
||||
|
||||
describe '#key_text' do
|
||||
it 'says pipeline by default' do
|
||||
expect(template.key_text).to eq 'pipeline'
|
||||
end
|
||||
|
||||
context 'when custom key_text is defined' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_text: 'custom text' })
|
||||
end
|
||||
|
||||
it 'returns custom value' do
|
||||
expect(template.key_text).to eq 'custom text'
|
||||
end
|
||||
|
||||
context 'when its size is larger than the max allowed value' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_text: 't' * 65 })
|
||||
end
|
||||
|
||||
it 'returns default value' do
|
||||
expect(template.key_text).to eq 'pipeline'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
it_behaves_like 'a badge template', 'pipeline'
|
||||
|
||||
describe '#value_text' do
|
||||
it 'is status value' do
|
||||
|
|
@ -38,32 +14,6 @@ RSpec.describe Gitlab::Ci::Badge::Pipeline::Template do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#key_width' do
|
||||
it 'is fixed by default' do
|
||||
expect(template.key_width).to eq 62
|
||||
end
|
||||
|
||||
context 'when custom key_width is defined' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_width: 101 })
|
||||
end
|
||||
|
||||
it 'returns custom value' do
|
||||
expect(template.key_width).to eq 101
|
||||
end
|
||||
|
||||
context 'when it is larger than the max allowed value' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_width: 513 })
|
||||
end
|
||||
|
||||
it 'returns default value' do
|
||||
expect(template.key_width).to eq 62
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'widths and text anchors' do
|
||||
it 'has fixed width and text anchors' do
|
||||
expect(template.width).to eq 116
|
||||
|
|
|
|||
|
|
@ -31,14 +31,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'job marked with chosen database' do
|
||||
it 'yields and sets database chosen', :aggregate_failures do
|
||||
expect { |b| middleware.call(worker, job, double(:queue), &b) }.to yield_control
|
||||
|
||||
expect(job[:database_chosen]).to eq('primary')
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'stick to the primary' do
|
||||
it 'sticks to the primary' do
|
||||
middleware.call(worker, job, double(:queue)) do
|
||||
|
|
@ -47,8 +39,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'replica is up to date' do |location|
|
||||
it 'do not stick to the primary', :aggregate_failures do
|
||||
shared_examples_for 'replica is up to date' do |location, data_consistency|
|
||||
it 'does not stick to the primary', :aggregate_failures do
|
||||
expect(middleware).to receive(:replica_caught_up?).with(location).and_return(true)
|
||||
|
||||
middleware.call(worker, job, double(:queue)) do
|
||||
|
|
@ -57,6 +49,12 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
|
|||
|
||||
expect(job[:database_chosen]).to eq('replica')
|
||||
end
|
||||
|
||||
it "updates job hash with data_consistency :#{data_consistency}" do
|
||||
middleware.call(worker, job, double(:queue)) do
|
||||
expect(job).to include(data_consistency: data_consistency.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'sticks based on data consistency' do |data_consistency|
|
||||
|
|
@ -77,7 +75,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
|
|||
allow(middleware).to receive(:replica_caught_up?).and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'replica is up to date', '0/D525E3A8'
|
||||
it_behaves_like 'replica is up to date', '0/D525E3A8', data_consistency
|
||||
end
|
||||
|
||||
context 'when database primary location is set' do
|
||||
|
|
@ -87,7 +85,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
|
|||
allow(middleware).to receive(:replica_caught_up?).and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'replica is up to date', '0/D525E3A8'
|
||||
it_behaves_like 'replica is up to date', '0/D525E3A8', data_consistency
|
||||
end
|
||||
|
||||
context 'when database location is not set' do
|
||||
|
|
@ -171,7 +169,12 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
|
|||
end
|
||||
|
||||
include_examples 'stick to the primary'
|
||||
include_examples 'job marked with chosen database'
|
||||
|
||||
it 'updates job hash with primary database chosen', :aggregate_failures do
|
||||
expect { |b| middleware.call(worker, job, double(:queue), &b) }.to yield_control
|
||||
|
||||
expect(job[:database_chosen]).to eq('primary')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -284,6 +284,18 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the optional trailers parameter' do
|
||||
it 'includes the Git trailers' do
|
||||
get api("/projects/#{project_id}/repository/commits?ref_name=6d394385cf567f80a8fd85055db1ab4c5295806f&trailers=true", current_user)
|
||||
|
||||
commit = json_response[0]
|
||||
|
||||
expect(commit['trailers']).to eq(
|
||||
'Signed-off-by' => 'Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -230,10 +230,6 @@ RSpec.configure do |config|
|
|||
Gitlab::Database.set_open_transactions_baseline
|
||||
end
|
||||
|
||||
config.append_before do
|
||||
Thread.current[:current_example_group] = ::RSpec.current_example.metadata[:example_group]
|
||||
end
|
||||
|
||||
config.append_after do
|
||||
Gitlab::Database.reset_open_transactions_baseline
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'parallel'
|
||||
|
||||
module TestEnv
|
||||
extend ActiveSupport::Concern
|
||||
extend self
|
||||
|
||||
ComponentFailedToInstallError = Class.new(StandardError)
|
||||
|
|
@ -95,40 +94,50 @@ module TestEnv
|
|||
TMP_TEST_PATH = Rails.root.join('tmp', 'tests').freeze
|
||||
REPOS_STORAGE = 'default'
|
||||
SECOND_STORAGE_PATH = Rails.root.join('tmp', 'tests', 'second_storage')
|
||||
SETUP_METHODS = %i[setup_gitaly setup_gitlab_shell setup_workhorse setup_factory_repo setup_forked_repo].freeze
|
||||
|
||||
# Can be overriden
|
||||
def setup_methods
|
||||
SETUP_METHODS
|
||||
end
|
||||
|
||||
# Test environment
|
||||
#
|
||||
# See gitlab.yml.example test section for paths
|
||||
#
|
||||
def init
|
||||
def init(opts = {})
|
||||
unless Rails.env.test?
|
||||
puts "\nTestEnv.init can only be run if `RAILS_ENV` is set to 'test' not '#{Rails.env}'!\n"
|
||||
exit 1
|
||||
end
|
||||
|
||||
start = Time.now
|
||||
# Disable mailer for spinach tests
|
||||
disable_mailer if opts[:mailer] == false
|
||||
|
||||
clean_test_path
|
||||
|
||||
# Install components in parallel as most of the setup is I/O.
|
||||
Parallel.each(setup_methods) do |method|
|
||||
public_send(method)
|
||||
end
|
||||
setup_gitlab_shell
|
||||
|
||||
post_init
|
||||
setup_gitaly
|
||||
|
||||
puts "\nTest environment set up in #{Time.now - start} seconds"
|
||||
# Feature specs are run through Workhorse
|
||||
setup_workhorse
|
||||
|
||||
# Create repository for FactoryBot.create(:project)
|
||||
setup_factory_repo
|
||||
|
||||
# Create repository for FactoryBot.create(:forked_project_with_submodules)
|
||||
setup_forked_repo
|
||||
end
|
||||
|
||||
# Can be overriden
|
||||
def post_init
|
||||
start_gitaly(gitaly_dir)
|
||||
included do |config|
|
||||
config.append_before do
|
||||
set_current_example_group
|
||||
end
|
||||
end
|
||||
|
||||
def disable_mailer
|
||||
allow_any_instance_of(NotificationService).to receive(:mailer)
|
||||
.and_return(double.as_null_object)
|
||||
end
|
||||
|
||||
def enable_mailer
|
||||
allow_any_instance_of(NotificationService).to receive(:mailer)
|
||||
.and_call_original
|
||||
end
|
||||
|
||||
# Clean /tmp/tests
|
||||
|
|
@ -155,11 +164,12 @@ module TestEnv
|
|||
end
|
||||
|
||||
def setup_gitaly
|
||||
install_gitaly_args = [gitaly_dir, repos_path, gitaly_url].compact.join(',')
|
||||
|
||||
component_timed_setup('Gitaly',
|
||||
install_dir: gitaly_dir,
|
||||
version: Gitlab::GitalyClient.expected_server_version,
|
||||
task: "gitlab:gitaly:install",
|
||||
task_args: [gitaly_dir, repos_path, gitaly_url].compact) do
|
||||
task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
|
||||
Gitlab::SetupHelper::Gitaly.create_configuration(
|
||||
gitaly_dir,
|
||||
{ 'default' => repos_path },
|
||||
|
|
@ -180,6 +190,8 @@ module TestEnv
|
|||
)
|
||||
Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true)
|
||||
end
|
||||
|
||||
start_gitaly(gitaly_dir)
|
||||
end
|
||||
|
||||
def gitaly_socket_path
|
||||
|
|
@ -261,18 +273,19 @@ module TestEnv
|
|||
raise "could not connect to #{service} at #{socket.inspect} after #{sleep_time} seconds"
|
||||
end
|
||||
|
||||
# Feature specs are run through Workhorse
|
||||
def setup_workhorse
|
||||
start = Time.now
|
||||
return if skip_compile_workhorse?
|
||||
|
||||
puts "\n==> Setting up GitLab Workhorse..."
|
||||
|
||||
FileUtils.rm_rf(workhorse_dir)
|
||||
Gitlab::SetupHelper::Workhorse.compile_into(workhorse_dir)
|
||||
Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil)
|
||||
|
||||
File.write(workhorse_tree_file, workhorse_tree) if workhorse_source_clean?
|
||||
|
||||
puts "==> GitLab Workhorse set up in #{Time.now - start} seconds...\n"
|
||||
puts " GitLab Workhorse set up in #{Time.now - start} seconds...\n"
|
||||
end
|
||||
|
||||
def skip_compile_workhorse?
|
||||
|
|
@ -336,12 +349,10 @@ module TestEnv
|
|||
ENV.fetch('GITLAB_WORKHORSE_URL', nil)
|
||||
end
|
||||
|
||||
# Create repository for FactoryBot.create(:project)
|
||||
def setup_factory_repo
|
||||
setup_repo(factory_repo_path, factory_repo_path_bare, factory_repo_name, BRANCH_SHA)
|
||||
end
|
||||
|
||||
# Create repository for FactoryBot.create(:forked_project_with_submodules)
|
||||
# This repo has a submodule commit that is not present in the main test
|
||||
# repository.
|
||||
def setup_forked_repo
|
||||
|
|
@ -352,18 +363,20 @@ module TestEnv
|
|||
clone_url = "https://gitlab.com/gitlab-org/#{repo_name}.git"
|
||||
|
||||
unless File.directory?(repo_path)
|
||||
puts "\n==> Setting up #{repo_name} repository in #{repo_path}..."
|
||||
start = Time.now
|
||||
system(*%W(#{Gitlab.config.git.bin_path} clone --quiet -- #{clone_url} #{repo_path}))
|
||||
puts "==> #{repo_path} set up in #{Time.now - start} seconds...\n"
|
||||
puts " #{repo_path} set up in #{Time.now - start} seconds...\n"
|
||||
end
|
||||
|
||||
set_repo_refs(repo_path, refs)
|
||||
|
||||
unless File.directory?(repo_path_bare)
|
||||
puts "\n==> Setting up #{repo_name} bare repository in #{repo_path_bare}..."
|
||||
start = Time.now
|
||||
# We must copy bare repositories because we will push to them.
|
||||
system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --quiet --bare -- #{repo_path} #{repo_path_bare}))
|
||||
puts "==> #{repo_path_bare} set up in #{Time.now - start} seconds...\n"
|
||||
puts " #{repo_path_bare} set up in #{Time.now - start} seconds...\n"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -455,6 +468,10 @@ module TestEnv
|
|||
|
||||
private
|
||||
|
||||
def set_current_example_group
|
||||
Thread.current[:current_example_group] = ::RSpec.current_example.metadata[:example_group]
|
||||
end
|
||||
|
||||
# These are directories that should be preserved at cleanup time
|
||||
def test_dirs
|
||||
@test_dirs ||= %w[
|
||||
|
|
@ -509,7 +526,7 @@ module TestEnv
|
|||
end
|
||||
end
|
||||
|
||||
def component_timed_setup(component, install_dir:, version:, task:, task_args: [])
|
||||
def component_timed_setup(component, install_dir:, version:, task:)
|
||||
start = Time.now
|
||||
|
||||
ensure_component_dir_name_is_correct!(component, install_dir)
|
||||
|
|
@ -518,16 +535,17 @@ module TestEnv
|
|||
return if File.exist?(install_dir) && ci?
|
||||
|
||||
if component_needs_update?(install_dir, version)
|
||||
puts "\n==> Setting up #{component}..."
|
||||
# Cleanup the component entirely to ensure we start fresh
|
||||
FileUtils.rm_rf(install_dir)
|
||||
|
||||
unless Rake::Task[task].invoke(*task_args)
|
||||
unless system('rake', task)
|
||||
raise ComponentFailedToInstallError
|
||||
end
|
||||
|
||||
yield if block_given?
|
||||
|
||||
puts "==> #{component} set up in #{Time.now - start} seconds...\n"
|
||||
puts " #{component} set up in #{Time.now - start} seconds...\n"
|
||||
end
|
||||
rescue ComponentFailedToInstallError
|
||||
puts "\n#{component} failed to install, cleaning up #{install_dir}!\n"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'a badge template' do |badge_type|
|
||||
describe '#key_text' do
|
||||
it "says #{badge_type} by default" do
|
||||
expect(template.key_text).to eq(badge_type)
|
||||
end
|
||||
|
||||
context 'when custom key_text is defined' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_text: "custom text" })
|
||||
end
|
||||
|
||||
it 'returns custom value' do
|
||||
expect(template.key_text).to eq("custom text")
|
||||
end
|
||||
|
||||
context 'when its size is larger than the max allowed value' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_text: 't' * (::Gitlab::Ci::Badge::Template::MAX_KEY_TEXT_SIZE + 1) } )
|
||||
end
|
||||
|
||||
it 'returns default value' do
|
||||
expect(template.key_text).to eq(badge_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#key_width' do
|
||||
let_it_be(:default_key_width) { ::Gitlab::Ci::Badge::Template::DEFAULT_KEY_WIDTH }
|
||||
|
||||
it 'is fixed by default' do
|
||||
expect(template.key_width).to eq(default_key_width)
|
||||
end
|
||||
|
||||
context 'when custom key_width is defined' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_width: 101 })
|
||||
end
|
||||
|
||||
it 'returns custom value' do
|
||||
expect(template.key_width).to eq(101)
|
||||
end
|
||||
|
||||
context 'when it is larger than the max allowed value' do
|
||||
before do
|
||||
allow(badge).to receive(:customization).and_return({ key_width: ::Gitlab::Ci::Badge::Template::MAX_KEY_WIDTH + 1 })
|
||||
end
|
||||
|
||||
it 'returns default value' do
|
||||
expect(template.key_width).to eq(default_key_width)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
39
yarn.lock
39
yarn.lock
|
|
@ -847,15 +847,15 @@
|
|||
exec-sh "^0.3.2"
|
||||
minimist "^1.2.0"
|
||||
|
||||
"@eslint/eslintrc@^0.4.1":
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.1.tgz#442763b88cecbe3ee0ec7ca6d6dd6168550cbf14"
|
||||
integrity sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==
|
||||
"@eslint/eslintrc@^0.4.2":
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.2.tgz#f63d0ef06f5c0c57d76c4ab5f63d3835c51b0179"
|
||||
integrity sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==
|
||||
dependencies:
|
||||
ajv "^6.12.4"
|
||||
debug "^4.1.1"
|
||||
espree "^7.3.0"
|
||||
globals "^12.1.0"
|
||||
globals "^13.9.0"
|
||||
ignore "^4.0.6"
|
||||
import-fresh "^3.2.1"
|
||||
js-yaml "^3.13.1"
|
||||
|
|
@ -5024,13 +5024,13 @@ eslint-visitor-keys@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
|
||||
integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
|
||||
|
||||
eslint@7.27.0:
|
||||
version "7.27.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.27.0.tgz#665a1506d8f95655c9274d84bd78f7166b07e9c7"
|
||||
integrity sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA==
|
||||
eslint@7.28.0:
|
||||
version "7.28.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.28.0.tgz#435aa17a0b82c13bb2be9d51408b617e49c1e820"
|
||||
integrity sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==
|
||||
dependencies:
|
||||
"@babel/code-frame" "7.12.11"
|
||||
"@eslint/eslintrc" "^0.4.1"
|
||||
"@eslint/eslintrc" "^0.4.2"
|
||||
ajv "^6.10.0"
|
||||
chalk "^4.0.0"
|
||||
cross-spawn "^7.0.2"
|
||||
|
|
@ -5047,7 +5047,7 @@ eslint@7.27.0:
|
|||
fast-deep-equal "^3.1.3"
|
||||
file-entry-cache "^6.0.1"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
glob-parent "^5.0.0"
|
||||
glob-parent "^5.1.2"
|
||||
globals "^13.6.0"
|
||||
ignore "^4.0.6"
|
||||
import-fresh "^3.0.0"
|
||||
|
|
@ -5732,7 +5732,7 @@ gettext-extractor@^3.5.3:
|
|||
pofile "1.0.x"
|
||||
typescript "2 - 4"
|
||||
|
||||
glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0:
|
||||
glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.0:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
|
|
@ -5812,17 +5812,10 @@ globals@^11.1.0:
|
|||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
|
||||
|
||||
globals@^12.1.0:
|
||||
version "12.3.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-12.3.0.tgz#1e564ee5c4dded2ab098b0f88f24702a3c56be13"
|
||||
integrity sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==
|
||||
dependencies:
|
||||
type-fest "^0.8.1"
|
||||
|
||||
globals@^13.6.0:
|
||||
version "13.8.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-13.8.0.tgz#3e20f504810ce87a8d72e55aecf8435b50f4c1b3"
|
||||
integrity sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==
|
||||
globals@^13.6.0, globals@^13.9.0:
|
||||
version "13.9.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb"
|
||||
integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==
|
||||
dependencies:
|
||||
type-fest "^0.20.2"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue