Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
704ed7ea39
commit
427dbb30f0
|
@ -49,15 +49,26 @@ export default {
|
||||||
:class="getMenuSectionClasses(sectionIndex)"
|
:class="getMenuSectionClasses(sectionIndex)"
|
||||||
data-testid="menu-section"
|
data-testid="menu-section"
|
||||||
>
|
>
|
||||||
<top-nav-menu-item
|
<template v-for="(menuItem, menuItemIndex) in menuItems">
|
||||||
v-for="(menuItem, menuItemIndex) in menuItems"
|
<strong
|
||||||
:key="menuItem.id"
|
v-if="menuItem.type == 'header'"
|
||||||
:menu-item="menuItem"
|
:key="menuItem.title"
|
||||||
data-testid="menu-item"
|
class="gl-px-4 gl-py-2 gl-text-gray-900 gl-display-block"
|
||||||
class="gl-w-full"
|
:class="{ 'gl-pt-3!': menuItemIndex > 0 }"
|
||||||
:class="{ 'gl-mt-1': menuItemIndex > 0 }"
|
data-testid="menu-header"
|
||||||
@click="onClick(menuItem)"
|
>
|
||||||
/>
|
{{ menuItem.title }}
|
||||||
|
</strong>
|
||||||
|
<top-nav-menu-item
|
||||||
|
v-else
|
||||||
|
:key="menuItem.id"
|
||||||
|
:menu-item="menuItem"
|
||||||
|
data-testid="menu-item"
|
||||||
|
class="gl-w-full"
|
||||||
|
:class="{ 'gl-mt-1': menuItemIndex > 0 }"
|
||||||
|
@click="onClick(menuItem)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { GitLabDropdown } from '~/deprecated_jquery_dropdown/gl_dropdown';
|
||||||
|
|
||||||
import initMergeRequest from '~/pages/projects/merge_requests/init_merge_request';
|
import initMergeRequest from '~/pages/projects/merge_requests/init_merge_request';
|
||||||
import initCheckFormState from './check_form_state';
|
import initCheckFormState from './check_form_state';
|
||||||
|
import initFormUpdate from './update_form';
|
||||||
|
|
||||||
function initTargetBranchSelector() {
|
function initTargetBranchSelector() {
|
||||||
const targetBranch = document.querySelector('.js-target-branch');
|
const targetBranch = document.querySelector('.js-target-branch');
|
||||||
|
@ -68,5 +69,6 @@ function initTargetBranchSelector() {
|
||||||
}
|
}
|
||||||
|
|
||||||
initMergeRequest();
|
initMergeRequest();
|
||||||
|
initFormUpdate();
|
||||||
initCheckFormState();
|
initCheckFormState();
|
||||||
initTargetBranchSelector();
|
initTargetBranchSelector();
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
const findForm = () => document.querySelector('.merge-request-form');
|
||||||
|
|
||||||
|
const removeHiddenCheckbox = (node) => {
|
||||||
|
const checkboxWrapper = node.closest('.form-check');
|
||||||
|
const hiddenCheckbox = checkboxWrapper.querySelector('input[type="hidden"]');
|
||||||
|
hiddenCheckbox.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const updateCheckboxes = () => {
|
||||||
|
const checkboxes = document.querySelectorAll('.js-form-update');
|
||||||
|
|
||||||
|
if (!checkboxes.length) return;
|
||||||
|
|
||||||
|
checkboxes.forEach((checkbox) => {
|
||||||
|
if (checkbox.checked) {
|
||||||
|
removeHiddenCheckbox(checkbox);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
findForm().addEventListener('submit', () => updateCheckboxes());
|
||||||
|
};
|
|
@ -385,7 +385,13 @@ export default {
|
||||||
@loadingSuccess="enableSwitchEditingControl"
|
@loadingSuccess="enableSwitchEditingControl"
|
||||||
@loadingError="enableSwitchEditingControl"
|
@loadingError="enableSwitchEditingControl"
|
||||||
/>
|
/>
|
||||||
<input id="wiki_content" v-model.trim="content" type="hidden" name="wiki[content]" />
|
<input
|
||||||
|
id="wiki_content"
|
||||||
|
v-model.trim="content"
|
||||||
|
type="hidden"
|
||||||
|
name="wiki[content]"
|
||||||
|
data-qa-selector="wiki_hidden_content"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
|
@ -10,6 +10,8 @@ import {
|
||||||
import fuzzaldrinPlus from 'fuzzaldrin-plus';
|
import fuzzaldrinPlus from 'fuzzaldrin-plus';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import { __, s__ } from '~/locale';
|
import { __, s__ } from '~/locale';
|
||||||
|
import Tracking from '~/tracking';
|
||||||
|
import { TRACKING_CATEGORIES } from '../../constants';
|
||||||
|
|
||||||
export const i18n = {
|
export const i18n = {
|
||||||
downloadArtifacts: __('Download artifacts'),
|
downloadArtifacts: __('Download artifacts'),
|
||||||
|
@ -29,6 +31,7 @@ export default {
|
||||||
GlSearchBoxByType,
|
GlSearchBoxByType,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
},
|
},
|
||||||
|
mixins: [Tracking.mixin()],
|
||||||
inject: {
|
inject: {
|
||||||
artifactsEndpoint: {
|
artifactsEndpoint: {
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -60,6 +63,10 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchArtifacts() {
|
fetchArtifacts() {
|
||||||
|
// refactor tracking based on action once this dropdown supports
|
||||||
|
// actions other than artifacts
|
||||||
|
this.track('click_artifacts_dropdown', { label: TRACKING_CATEGORIES.index });
|
||||||
|
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
// Replace the placeholder with the ID of the pipeline we are viewing
|
// Replace the placeholder with the ID of the pipeline we are viewing
|
||||||
const endpoint = this.artifactsEndpoint.replace(
|
const endpoint = this.artifactsEndpoint.replace(
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlTooltipDirective, GlModalDirective } from '@gitlab/ui';
|
import { GlButton, GlTooltipDirective, GlModalDirective } from '@gitlab/ui';
|
||||||
|
import Tracking from '~/tracking';
|
||||||
import eventHub from '../../event_hub';
|
import eventHub from '../../event_hub';
|
||||||
import { BUTTON_TOOLTIP_RETRY, BUTTON_TOOLTIP_CANCEL } from '../../constants';
|
import { BUTTON_TOOLTIP_RETRY, BUTTON_TOOLTIP_CANCEL, TRACKING_CATEGORIES } from '../../constants';
|
||||||
import PipelineMultiActions from './pipeline_multi_actions.vue';
|
import PipelineMultiActions from './pipeline_multi_actions.vue';
|
||||||
import PipelinesManualActions from './pipelines_manual_actions.vue';
|
import PipelinesManualActions from './pipelines_manual_actions.vue';
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ export default {
|
||||||
PipelineMultiActions,
|
PipelineMultiActions,
|
||||||
PipelinesManualActions,
|
PipelinesManualActions,
|
||||||
},
|
},
|
||||||
|
mixins: [Tracking.mixin()],
|
||||||
props: {
|
props: {
|
||||||
pipeline: {
|
pipeline: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -52,6 +54,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleCancelClick() {
|
handleCancelClick() {
|
||||||
|
this.trackClick('click_cancel_button');
|
||||||
eventHub.$emit('openConfirmationModal', {
|
eventHub.$emit('openConfirmationModal', {
|
||||||
pipeline: this.pipeline,
|
pipeline: this.pipeline,
|
||||||
endpoint: this.pipeline.cancel_path,
|
endpoint: this.pipeline.cancel_path,
|
||||||
|
@ -59,8 +62,12 @@ export default {
|
||||||
},
|
},
|
||||||
handleRetryClick() {
|
handleRetryClick() {
|
||||||
this.isRetrying = true;
|
this.isRetrying = true;
|
||||||
|
this.trackClick('click_retry_button');
|
||||||
eventHub.$emit('retryPipeline', this.pipeline.retry_path);
|
eventHub.$emit('retryPipeline', this.pipeline.retry_path);
|
||||||
},
|
},
|
||||||
|
trackClick(action) {
|
||||||
|
this.track(action, { label: TRACKING_CATEGORIES.index });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui';
|
import { GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
|
import Tracking from '~/tracking';
|
||||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||||
import { ICONS } from '../../constants';
|
import { ICONS, TRACKING_CATEGORIES } from '../../constants';
|
||||||
import PipelineLabels from './pipeline_labels.vue';
|
import PipelineLabels from './pipeline_labels.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -17,6 +18,7 @@ export default {
|
||||||
directives: {
|
directives: {
|
||||||
GlTooltip: GlTooltipDirective,
|
GlTooltip: GlTooltipDirective,
|
||||||
},
|
},
|
||||||
|
mixins: [Tracking.mixin()],
|
||||||
props: {
|
props: {
|
||||||
pipeline: {
|
pipeline: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -114,6 +116,11 @@ export default {
|
||||||
return this.pipeline?.commit?.title;
|
return this.pipeline?.commit?.title;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
trackClick(action) {
|
||||||
|
this.track(action, { label: TRACKING_CATEGORIES.index });
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
@ -125,6 +132,7 @@ export default {
|
||||||
:href="commitUrl"
|
:href="commitUrl"
|
||||||
class="commit-row-message gl-text-gray-900"
|
class="commit-row-message gl-text-gray-900"
|
||||||
data-testid="commit-title"
|
data-testid="commit-title"
|
||||||
|
@click="trackClick('click_commit_title')"
|
||||||
>{{ commitTitle }}</gl-link
|
>{{ commitTitle }}</gl-link
|
||||||
>
|
>
|
||||||
</tooltip-on-truncate>
|
</tooltip-on-truncate>
|
||||||
|
@ -137,6 +145,7 @@ export default {
|
||||||
class="gl-text-decoration-underline gl-text-blue-600! gl-mr-3"
|
class="gl-text-decoration-underline gl-text-blue-600! gl-mr-3"
|
||||||
data-testid="pipeline-url-link"
|
data-testid="pipeline-url-link"
|
||||||
data-qa-selector="pipeline_url_link"
|
data-qa-selector="pipeline_url_link"
|
||||||
|
@click="trackClick('click_pipeline_id')"
|
||||||
>#{{ pipeline[pipelineKey] }}</gl-link
|
>#{{ pipeline[pipelineKey] }}</gl-link
|
||||||
>
|
>
|
||||||
<!--Commit row-->
|
<!--Commit row-->
|
||||||
|
@ -154,11 +163,17 @@ export default {
|
||||||
:href="mergeRequestRef.path"
|
:href="mergeRequestRef.path"
|
||||||
class="ref-name gl-mr-3"
|
class="ref-name gl-mr-3"
|
||||||
data-testid="merge-request-ref"
|
data-testid="merge-request-ref"
|
||||||
|
@click="trackClick('click_mr_ref')"
|
||||||
>{{ mergeRequestRef.iid }}</gl-link
|
>{{ mergeRequestRef.iid }}</gl-link
|
||||||
>
|
>
|
||||||
<gl-link v-else :href="refUrl" class="ref-name gl-mr-3" data-testid="commit-ref-name">{{
|
<gl-link
|
||||||
commitRef.name
|
v-else
|
||||||
}}</gl-link>
|
:href="refUrl"
|
||||||
|
class="ref-name gl-mr-3"
|
||||||
|
data-testid="commit-ref-name"
|
||||||
|
@click="trackClick('click_commit_name')"
|
||||||
|
>{{ commitRef.name }}</gl-link
|
||||||
|
>
|
||||||
</tooltip-on-truncate>
|
</tooltip-on-truncate>
|
||||||
<gl-icon
|
<gl-icon
|
||||||
v-gl-tooltip
|
v-gl-tooltip
|
||||||
|
@ -167,9 +182,13 @@ export default {
|
||||||
:title="__('Commit')"
|
:title="__('Commit')"
|
||||||
data-testid="commit-icon"
|
data-testid="commit-icon"
|
||||||
/>
|
/>
|
||||||
<gl-link :href="commitUrl" class="commit-sha mr-0" data-testid="commit-short-sha">{{
|
<gl-link
|
||||||
commitShortSha
|
:href="commitUrl"
|
||||||
}}</gl-link>
|
class="commit-sha mr-0"
|
||||||
|
data-testid="commit-short-sha"
|
||||||
|
@click="trackClick('click_commit_sha')"
|
||||||
|
>{{ commitShortSha }}</gl-link
|
||||||
|
>
|
||||||
<user-avatar-link
|
<user-avatar-link
|
||||||
v-if="commitAuthor"
|
v-if="commitAuthor"
|
||||||
:link-href="commitAuthor.path"
|
:link-href="commitAuthor.path"
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
import { GlFilteredSearch } from '@gitlab/ui';
|
import { GlFilteredSearch } from '@gitlab/ui';
|
||||||
import { map } from 'lodash';
|
import { map } from 'lodash';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
|
import Tracking from '~/tracking';
|
||||||
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
|
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||||
|
import { TRACKING_CATEGORIES } from '../../constants';
|
||||||
import PipelineBranchNameToken from './tokens/pipeline_branch_name_token.vue';
|
import PipelineBranchNameToken from './tokens/pipeline_branch_name_token.vue';
|
||||||
import PipelineSourceToken from './tokens/pipeline_source_token.vue';
|
import PipelineSourceToken from './tokens/pipeline_source_token.vue';
|
||||||
import PipelineStatusToken from './tokens/pipeline_status_token.vue';
|
import PipelineStatusToken from './tokens/pipeline_status_token.vue';
|
||||||
|
@ -19,6 +21,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
GlFilteredSearch,
|
GlFilteredSearch,
|
||||||
},
|
},
|
||||||
|
mixins: [Tracking.mixin()],
|
||||||
props: {
|
props: {
|
||||||
projectId: {
|
projectId: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -110,6 +113,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onSubmit(filters) {
|
onSubmit(filters) {
|
||||||
|
this.track('click_filtered_search', { label: TRACKING_CATEGORIES.index });
|
||||||
this.$emit('filterPipelines', filters);
|
this.$emit('filterPipelines', filters);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,8 +4,10 @@ import createFlash from '~/flash';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||||
import { s__, __, sprintf } from '~/locale';
|
import { s__, __, sprintf } from '~/locale';
|
||||||
|
import Tracking from '~/tracking';
|
||||||
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
|
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
|
||||||
import eventHub from '../../event_hub';
|
import eventHub from '../../event_hub';
|
||||||
|
import { TRACKING_CATEGORIES } from '../../constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
directives: {
|
directives: {
|
||||||
|
@ -17,6 +19,7 @@ export default {
|
||||||
GlDropdownItem,
|
GlDropdownItem,
|
||||||
GlIcon,
|
GlIcon,
|
||||||
},
|
},
|
||||||
|
mixins: [Tracking.mixin()],
|
||||||
props: {
|
props: {
|
||||||
actions: {
|
actions: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -66,7 +69,6 @@ export default {
|
||||||
createFlash({ message: __('An error occurred while making the request.') });
|
createFlash({ message: __('An error occurred while making the request.') });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
isActionDisabled(action) {
|
isActionDisabled(action) {
|
||||||
if (action.playable === undefined) {
|
if (action.playable === undefined) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -74,6 +76,9 @@ export default {
|
||||||
|
|
||||||
return !action.playable;
|
return !action.playable;
|
||||||
},
|
},
|
||||||
|
trackClick() {
|
||||||
|
this.track('click_manual_actions', { label: TRACKING_CATEGORIES.index });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -86,6 +91,7 @@ export default {
|
||||||
right
|
right
|
||||||
lazy
|
lazy
|
||||||
icon="play"
|
icon="play"
|
||||||
|
@shown="trackClick"
|
||||||
>
|
>
|
||||||
<gl-dropdown-item
|
<gl-dropdown-item
|
||||||
v-for="action in actions"
|
v-for="action in actions"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { CHILD_VIEW } from '~/pipelines/constants';
|
import { CHILD_VIEW, TRACKING_CATEGORIES } from '~/pipelines/constants';
|
||||||
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
|
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
|
||||||
|
import Tracking from '~/tracking';
|
||||||
import PipelinesTimeago from './time_ago.vue';
|
import PipelinesTimeago from './time_ago.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -8,6 +9,7 @@ export default {
|
||||||
CiBadge,
|
CiBadge,
|
||||||
PipelinesTimeago,
|
PipelinesTimeago,
|
||||||
},
|
},
|
||||||
|
mixins: [Tracking.mixin()],
|
||||||
props: {
|
props: {
|
||||||
pipeline: {
|
pipeline: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -26,6 +28,11 @@ export default {
|
||||||
return this.viewType === CHILD_VIEW;
|
return this.viewType === CHILD_VIEW;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
trackClick() {
|
||||||
|
this.track('click_ci_status_badge', { label: TRACKING_CATEGORIES.index });
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -37,6 +44,7 @@ export default {
|
||||||
:show-text="!isChildView"
|
:show-text="!isChildView"
|
||||||
:icon-classes="'gl-vertical-align-middle!'"
|
:icon-classes="'gl-vertical-align-middle!'"
|
||||||
data-qa-selector="pipeline_commit_status"
|
data-qa-selector="pipeline_commit_status"
|
||||||
|
@ciStatusBadgeClick="trackClick"
|
||||||
/>
|
/>
|
||||||
<pipelines-timeago class="gl-mt-3" :pipeline="pipeline" />
|
<pipelines-timeago class="gl-mt-3" :pipeline="pipeline" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -109,3 +109,7 @@ export const DEFAULT_FIELDS = [
|
||||||
columnClass: 'gl-w-20p',
|
columnClass: 'gl-w-20p',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const TRACKING_CATEGORIES = {
|
||||||
|
index: 'pipelines_table_component',
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlTooltipDirective } from '@gitlab/ui';
|
import { GlTooltipDirective } from '@gitlab/ui';
|
||||||
|
import { visitUrl } from '~/lib/utils/url_utility';
|
||||||
import CiIcon from './ci_icon.vue';
|
import CiIcon from './ci_icon.vue';
|
||||||
/**
|
/**
|
||||||
* Renders CI Badge link with CI icon and status text based on
|
* Renders CI Badge link with CI icon and status text based on
|
||||||
|
@ -60,15 +61,24 @@ export default {
|
||||||
return className ? `ci-status ci-${className}` : 'ci-status';
|
return className ? `ci-status ci-${className}` : 'ci-status';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
navigateToPipeline() {
|
||||||
|
visitUrl(this.detailsPath);
|
||||||
|
|
||||||
|
// event used for tracking
|
||||||
|
this.$emit('ciStatusBadgeClick');
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
v-gl-tooltip
|
v-gl-tooltip
|
||||||
:href="detailsPath"
|
|
||||||
:class="cssClass"
|
:class="cssClass"
|
||||||
|
class="gl-cursor-pointer"
|
||||||
:title="title"
|
:title="title"
|
||||||
data-qa-selector="status_badge_link"
|
data-qa-selector="status_badge_link"
|
||||||
|
@click="navigateToPipeline"
|
||||||
>
|
>
|
||||||
<ci-icon :status="status" :css-classes="iconClasses" />
|
<ci-icon :status="status" :css-classes="iconClasses" />
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,9 @@ html {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
}
|
}
|
||||||
|
header {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
|
@ -28,7 +31,8 @@ hr {
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
h1 {
|
h1,
|
||||||
|
h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -49,26 +53,49 @@ img {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border-style: none;
|
border-style: none;
|
||||||
}
|
}
|
||||||
|
svg {
|
||||||
|
overflow: hidden;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
label {
|
label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
input {
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
input,
|
||||||
|
button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
|
button,
|
||||||
input {
|
input {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:not(:disabled),
|
||||||
|
[type="button"]:not(:disabled),
|
||||||
[type="submit"]:not(:disabled) {
|
[type="submit"]:not(:disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
[type="button"]::-moz-focus-inner,
|
||||||
[type="submit"]::-moz-focus-inner {
|
[type="submit"]::-moz-focus-inner {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-style: none;
|
border-style: none;
|
||||||
}
|
}
|
||||||
|
input[type="checkbox"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
fieldset {
|
fieldset {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -78,7 +105,8 @@ fieldset {
|
||||||
[hidden] {
|
[hidden] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
h1 {
|
h1,
|
||||||
|
h3 {
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
|
@ -87,6 +115,9 @@ h1 {
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2.1875rem;
|
font-size: 2.1875rem;
|
||||||
}
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 1.53125rem;
|
||||||
|
}
|
||||||
hr {
|
hr {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
@ -120,23 +151,42 @@ hr {
|
||||||
max-width: 1140px;
|
max-width: 1140px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.col-sm-12,
|
.row {
|
||||||
.col {
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-right: -15px;
|
||||||
|
margin-left: -15px;
|
||||||
|
}
|
||||||
|
.col-md-6,
|
||||||
|
.col-sm-12 {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
}
|
}
|
||||||
.col {
|
.order-1 {
|
||||||
flex-basis: 0;
|
order: 1;
|
||||||
flex-grow: 1;
|
}
|
||||||
max-width: 100%;
|
.order-12 {
|
||||||
|
order: 12;
|
||||||
}
|
}
|
||||||
@media (min-width: 576px) {
|
@media (min-width: 576px) {
|
||||||
.col-sm-12 {
|
.col-sm-12 {
|
||||||
flex: 0 0 100%;
|
flex: 0 0 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
.order-sm-1 {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
.order-sm-12 {
|
||||||
|
order: 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.col-md-6 {
|
||||||
|
flex: 0 0 50%;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.form-control {
|
.form-control {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -169,16 +219,6 @@ hr {
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
.form-row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-right: -5px;
|
|
||||||
margin-left: -5px;
|
|
||||||
}
|
|
||||||
.form-row > .col {
|
|
||||||
padding-right: 5px;
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
@ -204,6 +244,137 @@ hr {
|
||||||
fieldset:disabled a.btn {
|
fieldset:disabled a.btn {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
.btn-block {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.btn-block + .btn-block {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
input.btn-block[type="submit"],
|
||||||
|
input.btn-block[type="button"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.custom-control {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
min-height: 1.5rem;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
color-adjust: exact;
|
||||||
|
}
|
||||||
|
.custom-control-input {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.custom-control-input:checked ~ .custom-control-label::before {
|
||||||
|
color: #fff;
|
||||||
|
border-color: #007bff;
|
||||||
|
background-color: #007bff;
|
||||||
|
}
|
||||||
|
.custom-control-input:not(:disabled):active ~ .custom-control-label::before {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #b3d7ff;
|
||||||
|
border-color: #b3d7ff;
|
||||||
|
}
|
||||||
|
.custom-control-input:disabled ~ .custom-control-label {
|
||||||
|
color: #5e5e5e;
|
||||||
|
}
|
||||||
|
.custom-control-input:disabled ~ .custom-control-label::before {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
.custom-control-label {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.custom-control-label::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.25rem;
|
||||||
|
left: -1.5rem;
|
||||||
|
display: block;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
pointer-events: none;
|
||||||
|
content: "";
|
||||||
|
background-color: #fff;
|
||||||
|
border: #666 solid 1px;
|
||||||
|
}
|
||||||
|
.custom-control-label::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.25rem;
|
||||||
|
left: -1.5rem;
|
||||||
|
display: block;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
content: "";
|
||||||
|
background: no-repeat 50% / 50% 50%;
|
||||||
|
}
|
||||||
|
.custom-checkbox .custom-control-label::before {
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e");
|
||||||
|
}
|
||||||
|
.custom-checkbox
|
||||||
|
.custom-control-input:indeterminate
|
||||||
|
~ .custom-control-label::before {
|
||||||
|
border-color: #007bff;
|
||||||
|
background-color: #007bff;
|
||||||
|
}
|
||||||
|
.custom-checkbox
|
||||||
|
.custom-control-input:indeterminate
|
||||||
|
~ .custom-control-label::after {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e");
|
||||||
|
}
|
||||||
|
.custom-checkbox
|
||||||
|
.custom-control-input:disabled:checked
|
||||||
|
~ .custom-control-label::before {
|
||||||
|
background-color: rgba(0, 123, 255, 0.5);
|
||||||
|
}
|
||||||
|
.custom-checkbox
|
||||||
|
.custom-control-input:disabled:indeterminate
|
||||||
|
~ .custom-control-label::before {
|
||||||
|
background-color: rgba(0, 123, 255, 0.5);
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
}
|
||||||
|
.tab-content > .tab-pane {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.tab-content > .active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.navbar {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
|
.navbar .container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.clearfix::after {
|
||||||
|
display: block;
|
||||||
|
clear: both;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
.fixed-top {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1030;
|
||||||
|
}
|
||||||
.mt-3 {
|
.mt-3 {
|
||||||
margin-top: 1rem !important;
|
margin-top: 1rem !important;
|
||||||
}
|
}
|
||||||
|
@ -213,8 +384,8 @@ fieldset:disabled a.btn {
|
||||||
.text-nowrap {
|
.text-nowrap {
|
||||||
white-space: nowrap !important;
|
white-space: nowrap !important;
|
||||||
}
|
}
|
||||||
.text-center {
|
.font-weight-normal {
|
||||||
text-align: center !important;
|
font-weight: 400 !important;
|
||||||
}
|
}
|
||||||
.gl-form-input,
|
.gl-form-input,
|
||||||
.gl-form-input.form-control {
|
.gl-form-input.form-control {
|
||||||
|
@ -251,13 +422,103 @@ fieldset:disabled a.btn {
|
||||||
.gl-form-input.form-control::placeholder {
|
.gl-form-input.form-control::placeholder {
|
||||||
color: #868686;
|
color: #868686;
|
||||||
}
|
}
|
||||||
|
.gl-form-checkbox {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
color: #303030;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox .custom-control-input:disabled,
|
||||||
|
.gl-form-checkbox .custom-control-input:disabled ~ .custom-control-label {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: #868686;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control .custom-control-input ~ .custom-control-label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input
|
||||||
|
~ .custom-control-label::before,
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input
|
||||||
|
~ .custom-control-label::after {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input
|
||||||
|
~ .custom-control-label::before {
|
||||||
|
background-color: #fff;
|
||||||
|
border-color: #868686;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input:checked
|
||||||
|
~ .custom-control-label::before {
|
||||||
|
background-color: #1f75cb;
|
||||||
|
border-color: #1f75cb;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input[type="checkbox"]:checked
|
||||||
|
~ .custom-control-label::after,
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input[type="checkbox"]:indeterminate
|
||||||
|
~ .custom-control-label::after {
|
||||||
|
background: none;
|
||||||
|
background-color: #fff;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center center;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input[type="checkbox"]:checked
|
||||||
|
~ .custom-control-label::after {
|
||||||
|
mask-image: url('data:image/svg+xml,%3Csvg width="8" height="7" viewBox="0 0 8 7" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M1 3.05299L2.99123 5L7 1" stroke="white" stroke-width="2"/%3E%3C/svg%3E%0A');
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input[type="checkbox"]:indeterminate
|
||||||
|
~ .custom-control-label::after {
|
||||||
|
mask-image: url('data:image/svg+xml,%3Csvg width="8" height="2" viewBox="0 0 8 2" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M0 1L8 1" stroke="white" stroke-width="2"/%3E%3C/svg%3E%0A');
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control.custom-checkbox
|
||||||
|
.custom-control-input:indeterminate
|
||||||
|
~ .custom-control-label::before {
|
||||||
|
background-color: #1f75cb;
|
||||||
|
border-color: #1f75cb;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input:disabled
|
||||||
|
~ .custom-control-label {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input:disabled
|
||||||
|
~ .custom-control-label::before {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-color: #dbdbdb;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input:checked:disabled
|
||||||
|
~ .custom-control-label::before,
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input:indeterminate:disabled
|
||||||
|
~ .custom-control-label::before {
|
||||||
|
background-color: #dbdbdb;
|
||||||
|
border-color: #dbdbdb;
|
||||||
|
}
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input:checked:disabled
|
||||||
|
~ .custom-control-label::after,
|
||||||
|
.gl-form-checkbox.custom-control
|
||||||
|
.custom-control-input:indeterminate:disabled
|
||||||
|
~ .custom-control-label::after {
|
||||||
|
background-color: #5e5e5e;
|
||||||
|
}
|
||||||
.gl-button {
|
.gl-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
.gl-button:not(.btn-link):active {
|
.gl-button:not(.btn-link):active {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.gl-button.gl-button {
|
.gl-button.gl-button,
|
||||||
|
.gl-button.gl-button.btn-block {
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
|
@ -273,7 +534,8 @@ fieldset:disabled a.btn {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
.gl-button.gl-button .gl-button-text {
|
.gl-button.gl-button .gl-button-text,
|
||||||
|
.gl-button.gl-button.btn-block .gl-button-text {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -282,29 +544,39 @@ fieldset:disabled a.btn {
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
}
|
}
|
||||||
.gl-button.gl-button .gl-button-icon {
|
.gl-button.gl-button .gl-button-icon,
|
||||||
|
.gl-button.gl-button.btn-block .gl-button-icon {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
top: auto;
|
top: auto;
|
||||||
}
|
}
|
||||||
.gl-button.gl-button.btn-default {
|
.gl-button.gl-button.btn-default,
|
||||||
|
.gl-button.gl-button.btn-block.btn-default {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
.gl-button.gl-button.btn-default:active {
|
.gl-button.gl-button.btn-default:active,
|
||||||
|
.gl-button.gl-button.btn-default.active,
|
||||||
|
.gl-button.gl-button.btn-block.btn-default:active,
|
||||||
|
.gl-button.gl-button.btn-block.btn-default.active {
|
||||||
box-shadow: inset 0 0 0 1px #5e5e5e, 0 0 0 1px #fff, 0 0 0 3px #428fdc;
|
box-shadow: inset 0 0 0 1px #5e5e5e, 0 0 0 1px #fff, 0 0 0 3px #428fdc;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: #dbdbdb;
|
background-color: #dbdbdb;
|
||||||
}
|
}
|
||||||
.gl-button.gl-button.btn-confirm {
|
.gl-button.gl-button.btn-confirm,
|
||||||
|
.gl-button.gl-button.btn-block.btn-confirm {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.gl-button.gl-button.btn-confirm {
|
.gl-button.gl-button.btn-confirm,
|
||||||
|
.gl-button.gl-button.btn-block.btn-confirm {
|
||||||
background-color: #1f75cb;
|
background-color: #1f75cb;
|
||||||
box-shadow: inset 0 0 0 1px #1068bf;
|
box-shadow: inset 0 0 0 1px #1068bf;
|
||||||
}
|
}
|
||||||
.gl-button.gl-button.btn-confirm:active {
|
.gl-button.gl-button.btn-confirm:active,
|
||||||
|
.gl-button.gl-button.btn-confirm.active,
|
||||||
|
.gl-button.gl-button.btn-block.btn-confirm:active,
|
||||||
|
.gl-button.gl-button.btn-block.btn-confirm.active {
|
||||||
box-shadow: inset 0 0 0 1px #033464, 0 0 0 1px #fff, 0 0 0 3px #428fdc;
|
box-shadow: inset 0 0 0 1px #033464, 0 0 0 1px #fff, 0 0 0 3px #428fdc;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: #0b5cad;
|
background-color: #0b5cad;
|
||||||
|
@ -312,10 +584,14 @@ fieldset:disabled a.btn {
|
||||||
body {
|
body {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
[type="submit"] {
|
button,
|
||||||
|
html [type="button"],
|
||||||
|
[type="submit"],
|
||||||
|
[role="button"] {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
h1 {
|
h1,
|
||||||
|
h3 {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
@ -325,6 +601,9 @@ a {
|
||||||
hr {
|
hr {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
svg {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
.form-control {
|
.form-control {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
@ -332,9 +611,6 @@ hr {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
visibility: hidden !important;
|
visibility: hidden !important;
|
||||||
}
|
}
|
||||||
.hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
html {
|
html {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
@ -372,13 +648,34 @@ body.navless {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
.btn:active {
|
.btn:active,
|
||||||
|
.btn.active {
|
||||||
background-color: #eaeaea;
|
background-color: #eaeaea;
|
||||||
border-color: #e3e3e3;
|
border-color: #e3e3e3;
|
||||||
color: #303030;
|
color: #303030;
|
||||||
}
|
}
|
||||||
.light {
|
.btn svg {
|
||||||
color: #303030;
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
.btn svg:not(:last-child) {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.btn-block {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.btn-block.btn {
|
||||||
|
padding: 6px 0;
|
||||||
|
}
|
||||||
|
.tab-content {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.tab-content {
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hr {
|
hr {
|
||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
|
@ -416,6 +713,9 @@ input {
|
||||||
label {
|
label {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
label.custom-control-label {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
label.label-bold {
|
label.label-bold {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
@ -429,8 +729,25 @@ label.label-bold {
|
||||||
.gl-show-field-errors .form-control:not(textarea) {
|
.gl-show-field-errors .form-control:not(textarea) {
|
||||||
height: 34px;
|
height: 34px;
|
||||||
}
|
}
|
||||||
.gl-show-field-errors .gl-field-hint {
|
.navbar-empty {
|
||||||
color: #303030;
|
justify-content: center;
|
||||||
|
height: var(--header-height, 48px);
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #dbdbdb;
|
||||||
|
}
|
||||||
|
.navbar-empty .tanuki-logo,
|
||||||
|
.navbar-empty .brand-header-logo {
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
.tanuki-logo .tanuki {
|
||||||
|
fill: #e24329;
|
||||||
|
}
|
||||||
|
.tanuki-logo .left-cheek,
|
||||||
|
.tanuki-logo .right-cheek {
|
||||||
|
fill: #fc6d26;
|
||||||
|
}
|
||||||
|
.tanuki-logo .chin {
|
||||||
|
fill: #fca326;
|
||||||
}
|
}
|
||||||
input::-moz-placeholder {
|
input::-moz-placeholder {
|
||||||
color: #868686;
|
color: #868686;
|
||||||
|
@ -442,6 +759,9 @@ input::-ms-input-placeholder {
|
||||||
input:-ms-input-placeholder {
|
input:-ms-input-placeholder {
|
||||||
color: #868686;
|
color: #868686;
|
||||||
}
|
}
|
||||||
|
svg {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
.login-page .container {
|
.login-page .container {
|
||||||
max-width: 960px;
|
max-width: 960px;
|
||||||
}
|
}
|
||||||
|
@ -649,14 +969,17 @@ input:-ms-input-placeholder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.gl-text-green-600 {
|
.gl-display-flex {
|
||||||
color: #217645;
|
display: flex;
|
||||||
}
|
}
|
||||||
.gl-text-red-500 {
|
.gl-display-inline-block {
|
||||||
color: #dd2b0e;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.gl-display-block {
|
.gl-flex-wrap {
|
||||||
display: block;
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.gl-float-right {
|
||||||
|
float: right;
|
||||||
}
|
}
|
||||||
.gl-w-10 {
|
.gl-w-10 {
|
||||||
width: 3.5rem;
|
width: 3.5rem;
|
||||||
|
@ -675,14 +998,18 @@ input:-ms-input-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.gl-p-4 {
|
.gl-p-5 {
|
||||||
padding: 0.75rem;
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.gl-px-5 {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
}
|
}
|
||||||
.gl-pt-5 {
|
.gl-pt-5 {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
.gl-mt-2 {
|
.gl-mt-3 {
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
.gl-mt-5 {
|
.gl-mt-5 {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
@ -702,15 +1029,17 @@ input:-ms-input-placeholder {
|
||||||
.gl-mb-3 {
|
.gl-mb-3 {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
.gl-mb-5 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.gl-ml-auto {
|
.gl-ml-auto {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
.gl-ml-2 {
|
.gl-ml-2 {
|
||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.gl-sm-mt-0 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
.gl-text-center {
|
.gl-text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -720,6 +1049,9 @@ input:-ms-input-placeholder {
|
||||||
.gl-font-weight-normal {
|
.gl-font-weight-normal {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
.gl-font-weight-bold {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
@import "startup/cloaking";
|
@import "startup/cloaking";
|
||||||
@include cloak-startup-scss(none);
|
@include cloak-startup-scss(none);
|
||||||
|
|
|
@ -6,6 +6,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
||||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/226002 for more details.
|
# See https://gitlab.com/gitlab-org/gitlab/-/issues/226002 for more details.
|
||||||
|
|
||||||
include MetricsDashboard
|
include MetricsDashboard
|
||||||
|
include ProductAnalyticsTracking
|
||||||
|
|
||||||
layout 'project'
|
layout 'project'
|
||||||
|
|
||||||
|
@ -26,6 +27,18 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
||||||
before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
|
before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
|
||||||
after_action :expire_etag_cache, only: [:cancel_auto_stop]
|
after_action :expire_etag_cache, only: [:cancel_auto_stop]
|
||||||
|
|
||||||
|
track_event :index,
|
||||||
|
:folder,
|
||||||
|
:show,
|
||||||
|
:new,
|
||||||
|
:edit,
|
||||||
|
:create,
|
||||||
|
:update,
|
||||||
|
:stop,
|
||||||
|
:cancel_auto_stop,
|
||||||
|
:terminal,
|
||||||
|
name: 'users_visiting_environments_pages'
|
||||||
|
|
||||||
feature_category :continuous_delivery
|
feature_category :continuous_delivery
|
||||||
urgency :low
|
urgency :low
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,13 @@ module Nav
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def top_nav_localized_headers
|
||||||
|
{
|
||||||
|
explore: s_('TopNav|Explore'),
|
||||||
|
switch_to: s_('TopNav|Switch to')
|
||||||
|
}.freeze
|
||||||
|
end
|
||||||
|
|
||||||
def build_base_view_model(builder:, project:, group:)
|
def build_base_view_model(builder:, project:, group:)
|
||||||
if current_user
|
if current_user
|
||||||
build_view_model(builder: builder, project: project, group: group)
|
build_view_model(builder: builder, project: project, group: group)
|
||||||
|
@ -60,6 +67,7 @@ module Nav
|
||||||
# These come from `app/views/layouts/nav/_explore.html.ham`
|
# These come from `app/views/layouts/nav/_explore.html.ham`
|
||||||
if explore_nav_link?(:projects)
|
if explore_nav_link?(:projects)
|
||||||
builder.add_primary_menu_item_with_shortcut(
|
builder.add_primary_menu_item_with_shortcut(
|
||||||
|
header: top_nav_localized_headers[:explore],
|
||||||
href: explore_root_path,
|
href: explore_root_path,
|
||||||
active: nav == 'project' || active_nav_link?(path: %w[dashboard#show root#show projects#trending projects#starred projects#index]),
|
active: nav == 'project' || active_nav_link?(path: %w[dashboard#show root#show projects#trending projects#starred projects#index]),
|
||||||
**projects_menu_item_attrs
|
**projects_menu_item_attrs
|
||||||
|
@ -68,6 +76,7 @@ module Nav
|
||||||
|
|
||||||
if explore_nav_link?(:groups)
|
if explore_nav_link?(:groups)
|
||||||
builder.add_primary_menu_item_with_shortcut(
|
builder.add_primary_menu_item_with_shortcut(
|
||||||
|
header: top_nav_localized_headers[:explore],
|
||||||
href: explore_groups_path,
|
href: explore_groups_path,
|
||||||
active: nav == 'group' || active_nav_link?(controller: [:groups, 'groups/milestones', 'groups/group_members']),
|
active: nav == 'group' || active_nav_link?(controller: [:groups, 'groups/milestones', 'groups/group_members']),
|
||||||
**groups_menu_item_attrs
|
**groups_menu_item_attrs
|
||||||
|
@ -76,6 +85,7 @@ module Nav
|
||||||
|
|
||||||
if explore_nav_link?(:snippets)
|
if explore_nav_link?(:snippets)
|
||||||
builder.add_primary_menu_item_with_shortcut(
|
builder.add_primary_menu_item_with_shortcut(
|
||||||
|
header: top_nav_localized_headers[:explore],
|
||||||
active: active_nav_link?(controller: :snippets),
|
active: active_nav_link?(controller: :snippets),
|
||||||
href: explore_snippets_path,
|
href: explore_snippets_path,
|
||||||
**snippets_menu_item_attrs
|
**snippets_menu_item_attrs
|
||||||
|
@ -89,6 +99,7 @@ module Nav
|
||||||
current_item = project ? current_project(project: project) : {}
|
current_item = project ? current_project(project: project) : {}
|
||||||
|
|
||||||
builder.add_primary_menu_item_with_shortcut(
|
builder.add_primary_menu_item_with_shortcut(
|
||||||
|
header: top_nav_localized_headers[:switch_to],
|
||||||
active: nav == 'project' || active_nav_link?(path: %w[root#index projects#trending projects#starred dashboard/projects#index]),
|
active: nav == 'project' || active_nav_link?(path: %w[root#index projects#trending projects#starred dashboard/projects#index]),
|
||||||
css_class: 'qa-projects-dropdown',
|
css_class: 'qa-projects-dropdown',
|
||||||
data: { track_label: "projects_dropdown", track_action: "click_dropdown" },
|
data: { track_label: "projects_dropdown", track_action: "click_dropdown" },
|
||||||
|
@ -103,6 +114,7 @@ module Nav
|
||||||
current_item = group ? current_group(group: group) : {}
|
current_item = group ? current_group(group: group) : {}
|
||||||
|
|
||||||
builder.add_primary_menu_item_with_shortcut(
|
builder.add_primary_menu_item_with_shortcut(
|
||||||
|
header: top_nav_localized_headers[:switch_to],
|
||||||
active: nav == 'group' || active_nav_link?(path: %w[dashboard/groups explore/groups]),
|
active: nav == 'group' || active_nav_link?(path: %w[dashboard/groups explore/groups]),
|
||||||
css_class: 'qa-groups-dropdown',
|
css_class: 'qa-groups-dropdown',
|
||||||
data: { track_label: "groups_dropdown", track_action: "click_dropdown" },
|
data: { track_label: "groups_dropdown", track_action: "click_dropdown" },
|
||||||
|
@ -116,6 +128,7 @@ module Nav
|
||||||
if dashboard_nav_link?(:milestones)
|
if dashboard_nav_link?(:milestones)
|
||||||
builder.add_primary_menu_item_with_shortcut(
|
builder.add_primary_menu_item_with_shortcut(
|
||||||
id: 'milestones',
|
id: 'milestones',
|
||||||
|
header: top_nav_localized_headers[:explore],
|
||||||
title: _('Milestones'),
|
title: _('Milestones'),
|
||||||
href: dashboard_milestones_path,
|
href: dashboard_milestones_path,
|
||||||
active: active_nav_link?(controller: 'dashboard/milestones'),
|
active: active_nav_link?(controller: 'dashboard/milestones'),
|
||||||
|
@ -127,6 +140,7 @@ module Nav
|
||||||
|
|
||||||
if dashboard_nav_link?(:snippets)
|
if dashboard_nav_link?(:snippets)
|
||||||
builder.add_primary_menu_item_with_shortcut(
|
builder.add_primary_menu_item_with_shortcut(
|
||||||
|
header: top_nav_localized_headers[:explore],
|
||||||
active: active_nav_link?(controller: 'dashboard/snippets'),
|
active: active_nav_link?(controller: 'dashboard/snippets'),
|
||||||
data: { qa_selector: 'snippets_link', **menu_data_tracking_attrs('snippets') },
|
data: { qa_selector: 'snippets_link', **menu_data_tracking_attrs('snippets') },
|
||||||
href: dashboard_snippets_path,
|
href: dashboard_snippets_path,
|
||||||
|
@ -137,6 +151,7 @@ module Nav
|
||||||
if dashboard_nav_link?(:activity)
|
if dashboard_nav_link?(:activity)
|
||||||
builder.add_primary_menu_item_with_shortcut(
|
builder.add_primary_menu_item_with_shortcut(
|
||||||
id: 'activity',
|
id: 'activity',
|
||||||
|
header: top_nav_localized_headers[:explore],
|
||||||
title: _('Activity'),
|
title: _('Activity'),
|
||||||
href: activity_dashboard_path,
|
href: activity_dashboard_path,
|
||||||
active: active_nav_link?(path: 'dashboard#activity'),
|
active: active_nav_link?(path: 'dashboard#activity'),
|
||||||
|
|
|
@ -10,11 +10,7 @@ class ApplicationSetting < ApplicationRecord
|
||||||
|
|
||||||
ignore_columns %i[elasticsearch_shards elasticsearch_replicas], remove_with: '14.4', remove_after: '2021-09-22'
|
ignore_columns %i[elasticsearch_shards elasticsearch_replicas], remove_with: '14.4', remove_after: '2021-09-22'
|
||||||
ignore_columns %i[static_objects_external_storage_auth_token], remove_with: '14.9', remove_after: '2022-03-22'
|
ignore_columns %i[static_objects_external_storage_auth_token], remove_with: '14.9', remove_after: '2022-03-22'
|
||||||
ignore_column %i[max_package_files_for_package_destruction], remove_with: '14.9', remove_after: '2022-03-22'
|
|
||||||
ignore_column :user_email_lookup_limit, remove_with: '15.0', remove_after: '2022-04-18'
|
ignore_column :user_email_lookup_limit, remove_with: '15.0', remove_after: '2022-04-18'
|
||||||
ignore_column :pseudonymizer_enabled, remove_with: '15.1', remove_after: '2022-06-22'
|
|
||||||
ignore_column :enforce_ssh_key_expiration, remove_with: '15.2', remove_after: '2022-07-22'
|
|
||||||
ignore_column :enforce_pat_expiration, remove_with: '15.2', remove_after: '2022-07-22'
|
|
||||||
|
|
||||||
INSTANCE_REVIEW_MIN_USERS = 50
|
INSTANCE_REVIEW_MIN_USERS = 50
|
||||||
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
|
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
|
||||||
|
|
|
@ -1073,7 +1073,7 @@ module Ci
|
||||||
latest_test_report_builds.failed.limit(limit)
|
latest_test_report_builds.failed.limit(limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_reports?(reports_scope)
|
def complete_and_has_reports?(reports_scope)
|
||||||
if Feature.enabled?(:mr_show_reports_immediately, project, type: :development)
|
if Feature.enabled?(:mr_show_reports_immediately, project, type: :development)
|
||||||
latest_report_builds(reports_scope).exists?
|
latest_report_builds(reports_scope).exists?
|
||||||
else
|
else
|
||||||
|
@ -1090,7 +1090,7 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_generate_codequality_reports?
|
def can_generate_codequality_reports?
|
||||||
has_reports?(Ci::JobArtifact.of_report_type(:codequality))
|
complete_and_has_reports?(Ci::JobArtifact.of_report_type(:codequality))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_report_summary
|
def test_report_summary
|
||||||
|
@ -1313,7 +1313,7 @@ module Ci
|
||||||
|
|
||||||
def has_test_reports?
|
def has_test_reports?
|
||||||
strong_memoize(:has_test_reports) do
|
strong_memoize(:has_test_reports) do
|
||||||
has_reports?(::Ci::JobArtifact.of_report_type(:test))
|
complete_and_has_reports?(::Ci::JobArtifact.of_report_type(:test))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,15 +8,12 @@ module Ci
|
||||||
include ChronicDurationAttribute
|
include ChronicDurationAttribute
|
||||||
include FromUnion
|
include FromUnion
|
||||||
include TokenAuthenticatable
|
include TokenAuthenticatable
|
||||||
include IgnorableColumns
|
|
||||||
include FeatureGate
|
include FeatureGate
|
||||||
include Gitlab::Utils::StrongMemoize
|
include Gitlab::Utils::StrongMemoize
|
||||||
include TaggableQueries
|
include TaggableQueries
|
||||||
include Presentable
|
include Presentable
|
||||||
include EachBatch
|
include EachBatch
|
||||||
|
|
||||||
ignore_column :semver, remove_with: '15.4', remove_after: '2022-08-22'
|
|
||||||
|
|
||||||
add_authentication_token_field :token, encrypted: :optional, expires_at: :compute_token_expiration, expiration_enforced?: :token_expiration_enforced?
|
add_authentication_token_field :token, encrypted: :optional, expires_at: :compute_token_expiration, expiration_enforced?: :token_expiration_enforced?
|
||||||
|
|
||||||
enum access_level: {
|
enum access_level: {
|
||||||
|
|
|
@ -16,14 +16,10 @@ module Clusters
|
||||||
include ::Clusters::Concerns::ApplicationData
|
include ::Clusters::Concerns::ApplicationData
|
||||||
include AfterCommitQueue
|
include AfterCommitQueue
|
||||||
include UsageStatistics
|
include UsageStatistics
|
||||||
include IgnorableColumns
|
|
||||||
|
|
||||||
default_value_for :ingress_type, :nginx
|
default_value_for :ingress_type, :nginx
|
||||||
default_value_for :version, VERSION
|
default_value_for :version, VERSION
|
||||||
|
|
||||||
ignore_column :modsecurity_enabled, remove_with: '14.2', remove_after: '2021-07-22'
|
|
||||||
ignore_column :modsecurity_mode, remove_with: '14.2', remove_after: '2021-07-22'
|
|
||||||
|
|
||||||
enum ingress_type: {
|
enum ingress_type: {
|
||||||
nginx: 1
|
nginx: 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_metadata
|
def ensure_metadata
|
||||||
metadata || build_metadata(project: project)
|
metadata || build_metadata(project: project, partition_id: partition_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def degenerated?
|
def degenerated?
|
||||||
|
|
|
@ -1566,7 +1566,7 @@ class MergeRequest < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_test_reports?
|
def has_test_reports?
|
||||||
actual_head_pipeline&.has_reports?(Ci::JobArtifact.of_report_type(:test))
|
actual_head_pipeline&.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:test))
|
||||||
end
|
end
|
||||||
|
|
||||||
def predefined_variables
|
def predefined_variables
|
||||||
|
@ -1596,7 +1596,7 @@ class MergeRequest < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_accessibility_reports?
|
def has_accessibility_reports?
|
||||||
actual_head_pipeline.present? && actual_head_pipeline.has_reports?(Ci::JobArtifact.of_report_type(:accessibility))
|
actual_head_pipeline.present? && actual_head_pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:accessibility))
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_coverage_reports?
|
def has_coverage_reports?
|
||||||
|
@ -1604,7 +1604,7 @@ class MergeRequest < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_terraform_reports?
|
def has_terraform_reports?
|
||||||
actual_head_pipeline&.has_reports?(Ci::JobArtifact.of_report_type(:terraform))
|
actual_head_pipeline&.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:terraform))
|
||||||
end
|
end
|
||||||
|
|
||||||
def compare_accessibility_reports
|
def compare_accessibility_reports
|
||||||
|
@ -1644,7 +1644,7 @@ class MergeRequest < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_codequality_reports?
|
def has_codequality_reports?
|
||||||
actual_head_pipeline&.has_reports?(Ci::JobArtifact.of_report_type(:codequality))
|
actual_head_pipeline&.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:codequality))
|
||||||
end
|
end
|
||||||
|
|
||||||
def compare_codequality_reports
|
def compare_codequality_reports
|
||||||
|
@ -1694,11 +1694,11 @@ class MergeRequest < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_sast_reports?
|
def has_sast_reports?
|
||||||
!!actual_head_pipeline&.has_reports?(::Ci::JobArtifact.of_report_type(:sast))
|
!!actual_head_pipeline&.complete_and_has_reports?(::Ci::JobArtifact.of_report_type(:sast))
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_secret_detection_reports?
|
def has_secret_detection_reports?
|
||||||
!!actual_head_pipeline&.has_reports?(::Ci::JobArtifact.of_report_type(:secret_detection))
|
!!actual_head_pipeline&.complete_and_has_reports?(::Ci::JobArtifact.of_report_type(:secret_detection))
|
||||||
end
|
end
|
||||||
|
|
||||||
def compare_sast_reports(current_user)
|
def compare_sast_reports(current_user)
|
||||||
|
|
|
@ -51,8 +51,6 @@ class Project < ApplicationRecord
|
||||||
BoardLimitExceeded = Class.new(StandardError)
|
BoardLimitExceeded = Class.new(StandardError)
|
||||||
ExportLimitExceeded = Class.new(StandardError)
|
ExportLimitExceeded = Class.new(StandardError)
|
||||||
|
|
||||||
ignore_columns :mirror_last_update_at, :mirror_last_successful_update_at, remove_after: '2021-09-22', remove_with: '14.4'
|
|
||||||
ignore_columns :pull_mirror_branch_prefix, remove_after: '2021-09-22', remove_with: '14.4'
|
|
||||||
ignore_columns :build_coverage_regex, remove_after: '2022-10-22', remove_with: '15.5'
|
ignore_columns :build_coverage_regex, remove_after: '2022-10-22', remove_with: '15.5'
|
||||||
|
|
||||||
STATISTICS_ATTRIBUTE = 'repositories_count'
|
STATISTICS_ATTRIBUTE = 'repositories_count'
|
||||||
|
|
|
@ -23,6 +23,7 @@ module Ci
|
||||||
Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
|
Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
|
||||||
Gitlab::Ci::Pipeline::Chain::SeedBlock,
|
Gitlab::Ci::Pipeline::Chain::SeedBlock,
|
||||||
Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
|
Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
|
||||||
|
Gitlab::Ci::Pipeline::Chain::AssignPartition,
|
||||||
Gitlab::Ci::Pipeline::Chain::Seed,
|
Gitlab::Ci::Pipeline::Chain::Seed,
|
||||||
Gitlab::Ci::Pipeline::Chain::Limit::Size,
|
Gitlab::Ci::Pipeline::Chain::Limit::Size,
|
||||||
Gitlab::Ci::Pipeline::Chain::Limit::Deployments,
|
Gitlab::Ci::Pipeline::Chain::Limit::Deployments,
|
||||||
|
|
|
@ -10,7 +10,7 @@ module Ci
|
||||||
|
|
||||||
def execute(pipeline)
|
def execute(pipeline)
|
||||||
REPORT_TRACKED.each do |report|
|
REPORT_TRACKED.each do |report|
|
||||||
if pipeline.has_reports?(Ci::JobArtifact.of_report_type(report))
|
if pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(report))
|
||||||
track_usage_event(event_name(report), [pipeline.id, pipeline.user_id].join(VALUES_DELIMITER))
|
track_usage_event(event_name(report), [pipeline.id, pipeline.user_id].join(VALUES_DELIMITER))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,11 +39,13 @@ module Ci
|
||||||
job.pipeline = pipeline
|
job.pipeline = pipeline
|
||||||
job.project = pipeline.project
|
job.project = pipeline.project
|
||||||
job.ref = pipeline.ref
|
job.ref = pipeline.ref
|
||||||
|
job.partition_id = pipeline.partition_id
|
||||||
|
|
||||||
# update metadata since it might have been lazily initialised before this call
|
# update metadata since it might have been lazily initialised before this call
|
||||||
# metadata is present on `Ci::Processable`
|
# metadata is present on `Ci::Processable`
|
||||||
if job.respond_to?(:metadata) && job.metadata
|
if job.respond_to?(:metadata) && job.metadata
|
||||||
job.metadata.project = pipeline.project
|
job.metadata.project = pipeline.project
|
||||||
|
job.metadata.partition_id = pipeline.partition_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,20 +5,20 @@ module Issues
|
||||||
include Gitlab::Routing.url_helpers
|
include Gitlab::Routing.url_helpers
|
||||||
include GitlabRoutingHelper
|
include GitlabRoutingHelper
|
||||||
|
|
||||||
def initialize(issuables_relation, project)
|
def initialize(issuables_relation, project, user = nil)
|
||||||
super
|
super(issuables_relation, project)
|
||||||
|
|
||||||
@labels = @issuables.labels_hash.transform_values { |labels| labels.sort.join(',').presence }
|
@labels = @issuables.labels_hash.transform_values { |labels| labels.sort.join(',').presence }
|
||||||
end
|
end
|
||||||
|
|
||||||
def email(user)
|
def email(mail_to_user)
|
||||||
Notify.issues_csv_email(user, project, csv_data, csv_builder.status).deliver_now
|
Notify.issues_csv_email(mail_to_user, project, csv_data, csv_builder.status).deliver_now
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def associations_to_preload
|
def associations_to_preload
|
||||||
%i(author assignees timelogs milestone project)
|
[:author, :assignees, :timelogs, :milestone, { project: { namespace: :route } }]
|
||||||
end
|
end
|
||||||
|
|
||||||
def header_to_value_hash
|
def header_to_value_hash
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
#js-pipeline-tests-detail{ data: { summary_endpoint: summary_project_pipeline_tests_path(@project, @pipeline, format: :json),
|
#js-pipeline-tests-detail{ data: { summary_endpoint: summary_project_pipeline_tests_path(@project, @pipeline, format: :json),
|
||||||
suite_endpoint: project_pipeline_test_path(@project, @pipeline, suite_name: 'suite', format: :json),
|
suite_endpoint: project_pipeline_test_path(@project, @pipeline, suite_name: 'suite', format: :json),
|
||||||
blob_path: project_blob_path(@project, @pipeline.sha),
|
blob_path: project_blob_path(@project, @pipeline.sha),
|
||||||
has_test_report: @pipeline.has_reports?(Ci::JobArtifact.of_report_type(:test)).to_s,
|
has_test_report: @pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:test)).to_s,
|
||||||
empty_state_image_path: image_path('illustrations/empty-state/empty-test-cases-lg.svg'),
|
empty_state_image_path: image_path('illustrations/empty-state/empty-test-cases-lg.svg'),
|
||||||
artifacts_expired_image_path: image_path('illustrations/pipeline.svg') } }
|
artifacts_expired_image_path: image_path('illustrations/pipeline.svg') } }
|
||||||
= render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project
|
= render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
- if issuable.can_remove_source_branch?(current_user)
|
- if issuable.can_remove_source_branch?(current_user)
|
||||||
.form-check.gl-mb-3
|
.form-check.gl-mb-3
|
||||||
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
|
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
|
||||||
= check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input'
|
= check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input js-form-update'
|
||||||
= label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do
|
= label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do
|
||||||
= _("Delete source branch when merge request is accepted.")
|
= _("Delete source branch when merge request is accepted.")
|
||||||
- if !project.squash_never?
|
- if !project.squash_never?
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
= check_box_tag 'merge_request[squash]', '1', project.squash_enabled_by_default?, class: 'form-check-input', disabled: 'true'
|
= check_box_tag 'merge_request[squash]', '1', project.squash_enabled_by_default?, class: 'form-check-input', disabled: 'true'
|
||||||
- else
|
- else
|
||||||
= hidden_field_tag 'merge_request[squash]', '0', id: nil
|
= hidden_field_tag 'merge_request[squash]', '0', id: nil
|
||||||
= check_box_tag 'merge_request[squash]', '1', issuable_squash_option?(issuable, project), class: 'form-check-input'
|
= check_box_tag 'merge_request[squash]', '1', issuable_squash_option?(issuable, project), class: 'form-check-input js-form-update'
|
||||||
= label_tag 'merge_request[squash]', class: 'form-check-label' do
|
= label_tag 'merge_request[squash]', class: 'form-check-label' do
|
||||||
= _("Squash commits when merge request is accepted.")
|
= _("Squash commits when merge request is accepted.")
|
||||||
= link_to sprite_icon('question-o'), help_page_path('user/project/merge_requests/squash_and_merge'), target: '_blank', rel: 'noopener noreferrer'
|
= link_to sprite_icon('question-o'), help_page_path('user/project/merge_requests/squash_and_merge'), target: '_blank', rel: 'noopener noreferrer'
|
||||||
|
|
|
@ -127,3 +127,4 @@ end
|
||||||
|
|
||||||
Sidekiq::Scheduled::Poller.prepend Gitlab::Patch::SidekiqPoller
|
Sidekiq::Scheduled::Poller.prepend Gitlab::Patch::SidekiqPoller
|
||||||
Sidekiq::Cron::Poller.prepend Gitlab::Patch::SidekiqPoller
|
Sidekiq::Cron::Poller.prepend Gitlab::Patch::SidekiqPoller
|
||||||
|
Sidekiq::Cron::Poller.prepend Gitlab::Patch::SidekiqCronPoller
|
||||||
|
|
|
@ -1,3 +1,27 @@
|
||||||
|
# This file contains code based on the wikicloth project:
|
||||||
|
# https://github.com/nricciar/wikicloth
|
||||||
|
#
|
||||||
|
# Copyright (c) 2009 The wikicloth authors.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
# a copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
# permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
# the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
#
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'wikicloth'
|
require 'wikicloth'
|
||||||
|
@ -20,7 +44,10 @@ require 'digest/sha2'
|
||||||
# - https://gitlab.com/gitlab-org/gitlab/-/issues/361266
|
# - https://gitlab.com/gitlab-org/gitlab/-/issues/361266
|
||||||
|
|
||||||
# Guard to ensure we remember to delete this patch if they ever release a new version of wikicloth
|
# Guard to ensure we remember to delete this patch if they ever release a new version of wikicloth
|
||||||
raise 'New version of WikiCloth detected, please remove this patch' unless Gem::Version.new(WikiCloth::VERSION) == Gem::Version.new('0.8.1')
|
unless Gem::Version.new(WikiCloth::VERSION) == Gem::Version.new('0.8.1')
|
||||||
|
raise 'New version of WikiCloth detected, please either update the version for this check, ' \
|
||||||
|
'or remove this patch if no longer needed'
|
||||||
|
end
|
||||||
|
|
||||||
# rubocop:disable Style/ClassAndModuleChildren
|
# rubocop:disable Style/ClassAndModuleChildren
|
||||||
# rubocop:disable Layout/SpaceAroundEqualsInParameterDefault
|
# rubocop:disable Layout/SpaceAroundEqualsInParameterDefault
|
||||||
|
@ -43,6 +70,12 @@ raise 'New version of WikiCloth detected, please remove this patch' unless Gem::
|
||||||
# rubocop:disable Style/RegexpLiteralMixedPreserve
|
# rubocop:disable Style/RegexpLiteralMixedPreserve
|
||||||
# rubocop:disable Style/RedundantRegexpCharacterClass
|
# rubocop:disable Style/RedundantRegexpCharacterClass
|
||||||
# rubocop:disable Performance/StringInclude
|
# rubocop:disable Performance/StringInclude
|
||||||
|
# rubocop:disable Layout/LineLength
|
||||||
|
# rubocop:disable Style/RedundantSelf
|
||||||
|
# rubocop:disable Style/SymbolProc
|
||||||
|
# rubocop:disable Layout/SpaceInsideParens
|
||||||
|
# rubocop:disable Style/GuardClause
|
||||||
|
# rubocop:disable Style/RedundantRegexpEscape
|
||||||
module WikiCloth
|
module WikiCloth
|
||||||
class WikiCloth
|
class WikiCloth
|
||||||
def render(opt={})
|
def render(opt={})
|
||||||
|
@ -218,3 +251,9 @@ end
|
||||||
# rubocop:enable Style/RegexpLiteralMixedPreserve
|
# rubocop:enable Style/RegexpLiteralMixedPreserve
|
||||||
# rubocop:enable Style/RedundantRegexpCharacterClass
|
# rubocop:enable Style/RedundantRegexpCharacterClass
|
||||||
# rubocop:enable Performance/StringInclude
|
# rubocop:enable Performance/StringInclude
|
||||||
|
# rubocop:enable Layout/LineLength
|
||||||
|
# rubocop:enable Style/RedundantSelf
|
||||||
|
# rubocop:enable Style/SymbolProc
|
||||||
|
# rubocop:enable Layout/SpaceInsideParens
|
||||||
|
# rubocop:enable Style/GuardClause
|
||||||
|
# rubocop:enable Style/RedundantRegexpEscape
|
||||||
|
|
|
@ -0,0 +1,272 @@
|
||||||
|
# This file contains code based on the wikicloth project:
|
||||||
|
# https://github.com/nricciar/wikicloth
|
||||||
|
#
|
||||||
|
# Copyright (c) 2009 The wikicloth authors.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
# a copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
# permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
# the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'wikicloth'
|
||||||
|
require 'wikicloth/wiki_buffer/var'
|
||||||
|
|
||||||
|
# Adds patch for changes in this PRs:
|
||||||
|
#
|
||||||
|
# https://github.com/nricciar/wikicloth/pull/110
|
||||||
|
#
|
||||||
|
# The maintainers are not releasing new versions, so we
|
||||||
|
# need to patch it here.
|
||||||
|
#
|
||||||
|
# If they ever do release a version, then we can remove this file.
|
||||||
|
#
|
||||||
|
# See:
|
||||||
|
# - https://gitlab.com/gitlab-org/gitlab/-/issues/372400
|
||||||
|
|
||||||
|
# Guard to ensure we remember to delete this patch if they ever release a new version of wikicloth
|
||||||
|
unless Gem::Version.new(WikiCloth::VERSION) == Gem::Version.new('0.8.1')
|
||||||
|
raise 'New version of WikiCloth detected, please either update the version for this check, ' \
|
||||||
|
'or remove this patch if no longer needed'
|
||||||
|
end
|
||||||
|
|
||||||
|
# rubocop:disable Style/ClassAndModuleChildren
|
||||||
|
# rubocop:disable Style/HashSyntax
|
||||||
|
# rubocop:disable Layout/SpaceAfterComma
|
||||||
|
# rubocop:disable Style/RescueStandardError
|
||||||
|
# rubocop:disable Metrics/AbcSize
|
||||||
|
# rubocop:disable Metrics/CyclomaticComplexity
|
||||||
|
# rubocop:disable Metrics/PerceivedComplexity
|
||||||
|
# rubocop:disable Cop/LineBreakAroundConditionalBlock
|
||||||
|
# rubocop:disable Layout/EmptyLineAfterGuardClause
|
||||||
|
# rubocop:disable Performance/ReverseEach
|
||||||
|
# rubocop:disable Style/PerlBackrefs
|
||||||
|
# rubocop:disable Style/RedundantRegexpCharacterClass
|
||||||
|
# rubocop:disable Performance/StringInclude
|
||||||
|
# rubocop:disable Style/IfUnlessModifier
|
||||||
|
# rubocop:disable Layout/LineLength
|
||||||
|
# rubocop:disable Lint/DeprecatedClassMethods
|
||||||
|
# rubocop:disable Lint/UselessAssignment
|
||||||
|
# rubocop:disable Lint/RedundantStringCoercion
|
||||||
|
# rubocop:disable Style/StringLiteralsInInterpolation
|
||||||
|
# rubocop:disable Lint/UriEscapeUnescape
|
||||||
|
# rubocop:disable Style/For
|
||||||
|
# rubocop:disable Style/SlicingWithRange
|
||||||
|
# rubocop:disable Style/GuardClause
|
||||||
|
# rubocop:disable Style/ZeroLengthPredicate
|
||||||
|
# rubocop:disable Cop/LineBreakAfterGuardClauses
|
||||||
|
# rubocop:disable Layout/MultilineHashBraceLayout
|
||||||
|
module WikiCloth
|
||||||
|
class WikiCloth
|
||||||
|
class MathExtension < Extension
|
||||||
|
# <math>latex markup</math>
|
||||||
|
#
|
||||||
|
element 'math', :skip_html => true, :run_globals => false do |buffer|
|
||||||
|
blahtex_path = @options[:blahtex_path] || '/usr/bin/blahtex'
|
||||||
|
blahtex_png_path = @options[:blahtex_png_path] || '/tmp'
|
||||||
|
blahtex_options = @options[:blahtex_options] || '--texvc-compatible-commands --mathml-version-1-fonts --disallow-plane-1 --spacing strict'
|
||||||
|
|
||||||
|
if File.exists?(blahtex_path) && @options[:math_formatter] != :google
|
||||||
|
begin
|
||||||
|
# pass tex markup to blahtex
|
||||||
|
response = IO.popen("#{blahtex_path} #{blahtex_options} --png --mathml --png-directory #{blahtex_png_path}","w+") do |pipe|
|
||||||
|
pipe.write(buffer.element_content)
|
||||||
|
pipe.close_write
|
||||||
|
pipe.gets
|
||||||
|
end
|
||||||
|
|
||||||
|
xml_response = REXML::Document.new(response).root
|
||||||
|
|
||||||
|
if @options[:blahtex_html_prefix]
|
||||||
|
# render as embedded image
|
||||||
|
file_md5 = xml_response.elements["png/md5"].text
|
||||||
|
"<img src=\"#{File.join(@options[:blahtex_html_prefix],"#{file_md5}.png")}\" />"
|
||||||
|
else
|
||||||
|
# render as mathml
|
||||||
|
html = xml_response.elements["mathml/markup"].text
|
||||||
|
"<math xmlns=\"http://www.w3.org/1998/Math/MathML\">#{xml_response.elements["mathml/markup"].children.to_s}</math>"
|
||||||
|
end
|
||||||
|
rescue => err
|
||||||
|
# blahtex error
|
||||||
|
"<span class=\"error\">#{I18n.t("unable to parse mathml", :error => err)}</span>"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# if blahtex does not exist fallback to google charts api
|
||||||
|
# This is the patched line from:
|
||||||
|
# https://github.com/nricciar/wikicloth/pull/110/files#diff-f0cb4c400957bbdcc4c97d69d2aa7f48d8ba56c5943e484863f620605d7d17d4R37
|
||||||
|
encoded_string = URI.encode_www_form_component(buffer.element_content)
|
||||||
|
"<img src=\"https://chart.googleapis.com/chart?cht=tx&chl=#{encoded_string}\" />"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class WikiBuffer::Var < WikiBuffer
|
||||||
|
def default_functions(name,params)
|
||||||
|
case name
|
||||||
|
when "#if"
|
||||||
|
params.first.blank? ? params[2] : params[1]
|
||||||
|
when "#switch"
|
||||||
|
match = params.first
|
||||||
|
default = nil
|
||||||
|
for p in params[1..-1]
|
||||||
|
temp = p.split("=")
|
||||||
|
if p !~ /=/ && temp.length == 1 && p == params.last
|
||||||
|
return p
|
||||||
|
elsif temp.instance_of?(Array) && temp.length > 0
|
||||||
|
test = temp.first.strip
|
||||||
|
default = temp[1..-1].join("=").strip if test == "#default"
|
||||||
|
return temp[1..-1].join("=").strip if test == match || (test == "none" && match.blank?)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
default.nil? ? "" : default
|
||||||
|
when "#expr"
|
||||||
|
begin
|
||||||
|
ExpressionParser::Parser.new.parse(params.first)
|
||||||
|
rescue RuntimeError
|
||||||
|
I18n.t('expression error', :error => $!)
|
||||||
|
end
|
||||||
|
when "#ifexpr"
|
||||||
|
val = false
|
||||||
|
begin
|
||||||
|
val = ExpressionParser::Parser.new.parse(params.first)
|
||||||
|
rescue RuntimeError
|
||||||
|
end
|
||||||
|
if val
|
||||||
|
params[1]
|
||||||
|
else
|
||||||
|
params[2]
|
||||||
|
end
|
||||||
|
when "#ifeq"
|
||||||
|
if params[0] =~ /^[0-9A-Fa-f]+$/ && params[1] =~ /^[0-9A-Fa-f]+$/
|
||||||
|
params[0].to_i == params[1].to_i ? params[2] : params[3]
|
||||||
|
else
|
||||||
|
params[0] == params[1] ? params[2] : params[3]
|
||||||
|
end
|
||||||
|
when "#len"
|
||||||
|
params.first.length
|
||||||
|
when "#sub"
|
||||||
|
params.first[params[1].to_i,params[2].to_i]
|
||||||
|
when "#pad"
|
||||||
|
case params[3]
|
||||||
|
when "right"
|
||||||
|
params[0].ljust(params[1].to_i,params[2])
|
||||||
|
when "center"
|
||||||
|
params[0].center(params[1].to_i,params[2])
|
||||||
|
else
|
||||||
|
params[0].rjust(params[1].to_i,params[2])
|
||||||
|
end
|
||||||
|
when "#iferror"
|
||||||
|
params.first =~ /error/ ? params[1] : params[2]
|
||||||
|
when "#capture"
|
||||||
|
@options[:params][params.first] = params[1]
|
||||||
|
""
|
||||||
|
when "urlencode"
|
||||||
|
# This is the patched line from:
|
||||||
|
# https://github.com/nricciar/wikicloth/pull/110/files#diff-f262faf4fadb222cca87185be0fb65b3f49659abc840794cc83a736d41310fb1R170
|
||||||
|
URI.encode_www_form_component(params.first)
|
||||||
|
when "lc"
|
||||||
|
params.first.downcase
|
||||||
|
when "uc"
|
||||||
|
params.first.upcase
|
||||||
|
when "ucfirst"
|
||||||
|
params.first.capitalize
|
||||||
|
when "lcfirst"
|
||||||
|
params.first[0,1].downcase + params.first[1..-1]
|
||||||
|
when "anchorencode"
|
||||||
|
params.first.gsub(/\s+/,'_')
|
||||||
|
when "plural"
|
||||||
|
begin
|
||||||
|
expr_value = ExpressionParser::Parser.new.parse(params.first)
|
||||||
|
expr_value.to_i == 1 ? params[1] : params[2]
|
||||||
|
rescue RuntimeError
|
||||||
|
I18n.t('expression error', :error => $!)
|
||||||
|
end
|
||||||
|
when "ns"
|
||||||
|
values = {
|
||||||
|
"" => "", "0" => "",
|
||||||
|
"1" => localise_ns("Talk"), "talk" => localise_ns("Talk"),
|
||||||
|
"6" => localise_ns("File"), "file" => localise_ns("File"), "image" => localise_ns("File"),
|
||||||
|
"10" => localise_ns("Template"), "template" => localise_ns("Template"),
|
||||||
|
"14" => localise_ns("Category"), "category" => localise_ns("Category"),
|
||||||
|
"-1" => localise_ns("Special"), "special" => localise_ns("Special"),
|
||||||
|
"12" => localise_ns("Help"), "help" => localise_ns("Help"),
|
||||||
|
"-2" => localise_ns("Media"), "media" => localise_ns("Media") }
|
||||||
|
|
||||||
|
values[localise_ns(params.first,:en).gsub(/\s+/,'_').downcase]
|
||||||
|
when "#language"
|
||||||
|
WikiNamespaces.language_name(params.first)
|
||||||
|
when "#tag"
|
||||||
|
return "" if params.empty?
|
||||||
|
elem = Builder::XmlMarkup.new
|
||||||
|
return elem.tag!(params.first) if params.length == 1
|
||||||
|
return elem.tag!(params.first) { |e| e << params.last } if params.length == 2
|
||||||
|
tag_attrs = {}
|
||||||
|
params[1..-2].each do |attr|
|
||||||
|
tag_attrs[$1] = $2 if attr =~ /^\s*([\w]+)\s*=\s*"(.*)"\s*$/
|
||||||
|
end
|
||||||
|
elem.tag!(params.first,tag_attrs) { |e| e << params.last }
|
||||||
|
when "debug"
|
||||||
|
ret = nil
|
||||||
|
case params.first
|
||||||
|
when "param"
|
||||||
|
@options[:buffer].buffers.reverse.each do |b|
|
||||||
|
if b.instance_of?(WikiBuffer::HTMLElement) && b.element_name == "template"
|
||||||
|
ret = b.get_param(params[1])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ret
|
||||||
|
when "buffer"
|
||||||
|
ret = "<pre>"
|
||||||
|
buffer = @options[:buffer].buffers
|
||||||
|
buffer.each do |b|
|
||||||
|
ret += " --- #{b.class}"
|
||||||
|
ret += b.instance_of?(WikiBuffer::HTMLElement) ? " -- #{b.element_name}\n" : " -- #{b.data}\n"
|
||||||
|
end
|
||||||
|
"#{ret}</pre>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# rubocop:enable Style/ClassAndModuleChildren
|
||||||
|
# rubocop:enable Style/HashSyntax
|
||||||
|
# rubocop:enable Layout/SpaceAfterComma
|
||||||
|
# rubocop:enable Style/RescueStandardError
|
||||||
|
# rubocop:enable Metrics/AbcSize
|
||||||
|
# rubocop:enable Metrics/CyclomaticComplexity
|
||||||
|
# rubocop:enable Metrics/PerceivedComplexity
|
||||||
|
# rubocop:enable Cop/LineBreakAroundConditionalBlock
|
||||||
|
# rubocop:enable Layout/EmptyLineAfterGuardClause
|
||||||
|
# rubocop:enable Performance/ReverseEach
|
||||||
|
# rubocop:enable Style/PerlBackrefs
|
||||||
|
# rubocop:enable Style/RedundantRegexpCharacterClass
|
||||||
|
# rubocop:enable Performance/StringInclude
|
||||||
|
# rubocop:enable Style/IfUnlessModifier
|
||||||
|
# rubocop:enable Layout/LineLength
|
||||||
|
# rubocop:enable Lint/DeprecatedClassMethods
|
||||||
|
# rubocop:enable Lint/UselessAssignment
|
||||||
|
# rubocop:enable Lint/RedundantStringCoercion
|
||||||
|
# rubocop:enable Style/StringLiteralsInInterpolation
|
||||||
|
# rubocop:enable Lint/UriEscapeUnescape
|
||||||
|
# rubocop:enable Style/For
|
||||||
|
# rubocop:enable Style/SlicingWithRange
|
||||||
|
# rubocop:enable Style/GuardClause
|
||||||
|
# rubocop:enable Style/ZeroLengthPredicate
|
||||||
|
# rubocop:enable Cop/LineBreakAfterGuardClauses
|
||||||
|
# rubocop:enable Layout/MultilineHashBraceLayout
|
|
@ -48,7 +48,6 @@ tier:
|
||||||
- premium
|
- premium
|
||||||
- ultimate
|
- ultimate
|
||||||
performance_indicator_type:
|
performance_indicator_type:
|
||||||
- smau
|
|
||||||
- gmau
|
- gmau
|
||||||
- paid_gmau
|
- paid_gmau
|
||||||
milestone: "<13.9"
|
milestone: "<13.9"
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
key_path: redis_hll_counters.environments.users_visiting_environments_pages_monthly
|
||||||
|
description: Monthly count of unique users visiting environments pages
|
||||||
|
product_section: ops
|
||||||
|
product_stage: release
|
||||||
|
product_group: release
|
||||||
|
product_category: environment_management
|
||||||
|
value_type: number
|
||||||
|
status: active
|
||||||
|
milestone: "15.4"
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97063
|
||||||
|
time_frame: 28d
|
||||||
|
data_source: redis_hll
|
||||||
|
data_category: optional
|
||||||
|
instrumentation_class: RedisHLLMetric
|
||||||
|
performance_indicator_type: []
|
||||||
|
options:
|
||||||
|
events:
|
||||||
|
- users_visiting_environments_pages
|
||||||
|
distribution:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tier:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
|
@ -13,7 +13,8 @@ time_frame: all
|
||||||
data_source: database
|
data_source: database
|
||||||
instrumentation_class: CountUserAuthMetric
|
instrumentation_class: CountUserAuthMetric
|
||||||
data_category: optional
|
data_category: optional
|
||||||
performance_indicator_type: []
|
performance_indicator_type:
|
||||||
|
- smau
|
||||||
distribution:
|
distribution:
|
||||||
- ce
|
- ce
|
||||||
- ee
|
- ee
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddColumnBranchFilterStrategyToWebHooks < Gitlab::Database::Migration[2.0]
|
||||||
|
def change
|
||||||
|
add_column :web_hooks, :branch_filter_strategy, :integer, null: false, default: 0, limit: 2
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
394f346e3a93f8a6b74fd0461eb59f569c6a18f90ae653c330a38e3a3706b5f6
|
|
@ -22922,7 +22922,8 @@ CREATE TABLE web_hooks (
|
||||||
disabled_until timestamp with time zone,
|
disabled_until timestamp with time zone,
|
||||||
encrypted_url_variables bytea,
|
encrypted_url_variables bytea,
|
||||||
encrypted_url_variables_iv bytea,
|
encrypted_url_variables_iv bytea,
|
||||||
integration_id integer
|
integration_id integer,
|
||||||
|
branch_filter_strategy smallint DEFAULT 0 NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE SEQUENCE web_hooks_id_seq
|
CREATE SEQUENCE web_hooks_id_seq
|
||||||
|
|
|
@ -744,8 +744,8 @@ You can send backups to a locally-mounted share (for example, `NFS`,`CIFS`, or `
|
||||||
|
|
||||||
To do this, you must set the following configuration keys:
|
To do this, you must set the following configuration keys:
|
||||||
|
|
||||||
- `backup_upload_remote_directory`: mounted directory that backups are copied to.
|
- `backup_upload_connection.local_root`: mounted directory that backups are copied to.
|
||||||
- `backup_upload_connection.local_root`: subdirectory of the `backup_upload_remote_directory` directory. It is created if it doesn't exist.
|
- `backup_upload_remote_directory`: subdirectory of the `backup_upload_connection.local_root` directory. It is created if it doesn't exist.
|
||||||
If you want to copy the tarballs to the root of your mounted directory, use `.`.
|
If you want to copy the tarballs to the root of your mounted directory, use `.`.
|
||||||
|
|
||||||
When mounted, the directory set in the `local_root` key must be owned by either:
|
When mounted, the directory set in the `local_root` key must be owned by either:
|
||||||
|
|
|
@ -201,7 +201,7 @@ role on an ancestor group, add the user to the subgroup again with a higher role
|
||||||
## Mention subgroups
|
## Mention subgroups
|
||||||
|
|
||||||
Mentioning subgroups ([`@<subgroup_name>`](../../discussions/index.md#mentions)) in issues, commits, and merge requests
|
Mentioning subgroups ([`@<subgroup_name>`](../../discussions/index.md#mentions)) in issues, commits, and merge requests
|
||||||
notifies all members of that group. Mentioning works the same as for projects and groups, and you can choose the group
|
notifies all direct members of that group. Inherited members of a sub-group are not notified by mentions. Mentioning works the same as for projects and groups, and you can choose the group
|
||||||
of people to be notified.
|
of people to be notified.
|
||||||
|
|
||||||
<!-- ## Troubleshooting
|
<!-- ## Troubleshooting
|
||||||
|
|
|
@ -27,9 +27,33 @@ module API
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources 'batched_background_migrations/:id/resume' do
|
||||||
|
desc 'Resume a batched background migration'
|
||||||
|
params do
|
||||||
|
optional :database,
|
||||||
|
type: String,
|
||||||
|
values: Gitlab::Database.all_database_names,
|
||||||
|
desc: 'The name of the database',
|
||||||
|
default: 'main'
|
||||||
|
requires :id,
|
||||||
|
type: Integer,
|
||||||
|
desc: 'The batched background migration id'
|
||||||
|
end
|
||||||
|
put do
|
||||||
|
Gitlab::Database::SharedModel.using_connection(base_model.connection) do
|
||||||
|
batched_background_migration.execute!
|
||||||
|
present_entity(batched_background_migration)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
helpers do
|
helpers do
|
||||||
|
def batched_background_migration
|
||||||
|
@batched_background_migration ||= Gitlab::Database::BackgroundMigration::BatchedMigration.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
def base_model
|
def base_model
|
||||||
database = params[:database] || Gitlab::Database::MAIN_DATABASE_NAME
|
database = params[:database] || Gitlab::Database::MAIN_DATABASE_NAME
|
||||||
@base_model ||= Gitlab::Database.database_base_models[database]
|
@base_model ||= Gitlab::Database.database_base_models[database]
|
||||||
|
|
|
@ -34,9 +34,9 @@ module Gitlab
|
||||||
|
|
||||||
def expand_value(value)
|
def expand_value(value)
|
||||||
if value.is_a?(Hash)
|
if value.is_a?(Hash)
|
||||||
{ value: value[:value].to_s, description: value[:description] }
|
{ value: value[:value].to_s, description: value[:description] }.compact
|
||||||
else
|
else
|
||||||
{ value: value.to_s, description: nil }
|
{ value: value.to_s }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,7 +30,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def value_with_data
|
def value_with_data
|
||||||
{ value: @config.to_s, description: nil }
|
{ value: @config.to_s }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def value_with_data
|
def value_with_data
|
||||||
{ value: value, description: config_description }
|
{ value: value, description: config_description }.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
def config_value
|
def config_value
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Ci
|
||||||
|
module Pipeline
|
||||||
|
module Chain
|
||||||
|
class AssignPartition < Chain::Base
|
||||||
|
include Chain::Helpers
|
||||||
|
|
||||||
|
DEFAULT_PARTITION_ID = 100
|
||||||
|
|
||||||
|
def perform!
|
||||||
|
@pipeline.partition_id = find_partition_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def break?
|
||||||
|
@pipeline.errors.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# TODO handle parent-child pipelines
|
||||||
|
def find_partition_id
|
||||||
|
DEFAULT_PARTITION_ID
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -148,7 +148,9 @@ module Gitlab
|
||||||
ref: @pipeline.ref,
|
ref: @pipeline.ref,
|
||||||
tag: @pipeline.tag,
|
tag: @pipeline.tag,
|
||||||
trigger_request: @pipeline.legacy_trigger,
|
trigger_request: @pipeline.legacy_trigger,
|
||||||
protected: @pipeline.protected_ref?
|
protected: @pipeline.protected_ref?,
|
||||||
|
partition_id: @pipeline.partition_id,
|
||||||
|
metadata_attributes: { partition_id: @pipeline.partition_id }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,8 @@ module Gitlab
|
||||||
{ name: @attributes.fetch(:name),
|
{ name: @attributes.fetch(:name),
|
||||||
position: @attributes.fetch(:index),
|
position: @attributes.fetch(:index),
|
||||||
pipeline: @pipeline,
|
pipeline: @pipeline,
|
||||||
project: @pipeline.project }
|
project: @pipeline.project,
|
||||||
|
partition_id: @pipeline.partition_id }
|
||||||
end
|
end
|
||||||
|
|
||||||
def seeds
|
def seeds
|
||||||
|
|
|
@ -8,6 +8,7 @@ module Gitlab
|
||||||
BATCH_CLASS_MODULE = "#{JOB_CLASS_MODULE}::BatchingStrategies"
|
BATCH_CLASS_MODULE = "#{JOB_CLASS_MODULE}::BatchingStrategies"
|
||||||
MAXIMUM_FAILED_RATIO = 0.5
|
MAXIMUM_FAILED_RATIO = 0.5
|
||||||
MINIMUM_JOBS = 50
|
MINIMUM_JOBS = 50
|
||||||
|
FINISHED_PROGRESS_VALUE = 100
|
||||||
|
|
||||||
self.table_name = :batched_background_migrations
|
self.table_name = :batched_background_migrations
|
||||||
|
|
||||||
|
@ -232,7 +233,15 @@ module Gitlab
|
||||||
"BatchedMigration[id: #{id}]"
|
"BatchedMigration[id: #{id}]"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Computes an estimation of the progress of the migration in percents.
|
||||||
|
#
|
||||||
|
# Because `total_tuple_count` is an estimation of the tuples based on DB statistics
|
||||||
|
# when the migration is complete there can actually be more or less tuples that initially
|
||||||
|
# estimated as `total_tuple_count` so the progress may not show 100%. For that reason when
|
||||||
|
# we know migration completed successfully, we just return the 100 value
|
||||||
def progress
|
def progress
|
||||||
|
return FINISHED_PROGRESS_VALUE if finished?
|
||||||
|
|
||||||
return unless total_tuple_count.to_i > 0
|
return unless total_tuple_count.to_i > 0
|
||||||
|
|
||||||
100 * migrated_tuple_count / total_tuple_count
|
100 * migrated_tuple_count / total_tuple_count
|
||||||
|
|
|
@ -6,9 +6,15 @@ module Gitlab
|
||||||
def initialize
|
def initialize
|
||||||
@primary = []
|
@primary = []
|
||||||
@secondary = []
|
@secondary = []
|
||||||
|
@last_header_added = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_primary_menu_item(**args)
|
def add_primary_menu_item(header: nil, **args)
|
||||||
|
if header && (header != @last_header_added)
|
||||||
|
add_menu_header(dest: @primary, title: header)
|
||||||
|
@last_header_added = header
|
||||||
|
end
|
||||||
|
|
||||||
add_menu_item(dest: @primary, **args)
|
add_menu_item(dest: @primary, **args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -30,6 +36,12 @@ module Gitlab
|
||||||
|
|
||||||
dest.push(item)
|
dest.push(item)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_menu_header(dest:, **args)
|
||||||
|
header = ::Gitlab::Nav::TopNavMenuHeader.build(**args)
|
||||||
|
|
||||||
|
dest.push(header)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Nav
|
||||||
|
class TopNavMenuHeader
|
||||||
|
def self.build(title:)
|
||||||
|
{
|
||||||
|
type: :header,
|
||||||
|
title: title
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,6 +11,7 @@ module Gitlab
|
||||||
def self.build(id:, title:, active: false, icon: '', href: '', view: '', css_class: nil, data: nil, emoji: nil)
|
def self.build(id:, title:, active: false, icon: '', href: '', view: '', css_class: nil, data: nil, emoji: nil)
|
||||||
{
|
{
|
||||||
id: id,
|
id: id,
|
||||||
|
type: :item,
|
||||||
title: title,
|
title: title,
|
||||||
active: active,
|
active: active,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Patch to address https://github.com/ondrejbartas/sidekiq-cron/issues/361
|
||||||
|
# This restores the poll interval to v1.2.0 behavior
|
||||||
|
# https://github.com/ondrejbartas/sidekiq-cron/blob/v1.2.0/lib/sidekiq/cron/poller.rb#L36-L38
|
||||||
|
# This patch only applies to v1.4.0
|
||||||
|
require 'sidekiq/cron/version'
|
||||||
|
|
||||||
|
if Gem::Version.new(Sidekiq::Cron::VERSION) != Gem::Version.new('1.4.0')
|
||||||
|
raise 'New version of sidekiq-cron detected, please remove or update this patch'
|
||||||
|
end
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Patch
|
||||||
|
module SidekiqCronPoller
|
||||||
|
def poll_interval_average
|
||||||
|
Sidekiq.options[:poll_interval] || Sidekiq::Cron::POLL_INTERVAL
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -357,3 +357,8 @@
|
||||||
category: manage
|
category: manage
|
||||||
aggregation: weekly
|
aggregation: weekly
|
||||||
expiry: 42
|
expiry: 42
|
||||||
|
# Environments page
|
||||||
|
- name: users_visiting_environments_pages
|
||||||
|
category: environments
|
||||||
|
redis_slot: users
|
||||||
|
aggregation: weekly
|
||||||
|
|
|
@ -41407,9 +41407,18 @@ msgstr ""
|
||||||
msgid "Too many users found. Quick actions are limited to at most %{max_count} users"
|
msgid "Too many users found. Quick actions are limited to at most %{max_count} users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "TopNav|Explore"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "TopNav|Go back"
|
msgid "TopNav|Go back"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "TopNav|Switch to"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "TopNav|Your dashboard"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Topic %{source_topic} was successfully merged into topic %{target_topic}."
|
msgid "Topic %{source_topic} was successfully merged into topic %{target_topic}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,10 @@ module QA
|
||||||
base.view 'app/assets/javascripts/content_editor/components/toolbar_image_button.vue' do
|
base.view 'app/assets/javascripts/content_editor/components/toolbar_image_button.vue' do
|
||||||
element :file_upload_field
|
element :file_upload_field
|
||||||
end
|
end
|
||||||
|
|
||||||
|
base.view 'app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue' do
|
||||||
|
element :wiki_hidden_content
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_heading(heading, text)
|
def add_heading(heading, text)
|
||||||
|
@ -41,6 +45,13 @@ module QA
|
||||||
text_area.send_keys(:return)
|
text_area.send_keys(:return)
|
||||||
find_element(:file_upload_field, visible: false).send_keys(image_path)
|
find_element(:file_upload_field, visible: false).send_keys(image_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
QA::Support::Retrier.retry_on_exception do
|
||||||
|
source = find_element(:wiki_hidden_content, visible: false)
|
||||||
|
source.value =~ %r{uploads/.*#{::File.basename(image_path)}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -41,6 +41,7 @@ const ROOT_RAILS = IS_EE ? path.join(ROOT, 'ee') : ROOT;
|
||||||
const FIXTURES_FOLDER_NAME = IS_EE ? 'fixtures-ee' : 'fixtures';
|
const FIXTURES_FOLDER_NAME = IS_EE ? 'fixtures-ee' : 'fixtures';
|
||||||
const FIXTURES_ROOT = path.join(ROOT, 'tmp/tests/frontend', FIXTURES_FOLDER_NAME);
|
const FIXTURES_ROOT = path.join(ROOT, 'tmp/tests/frontend', FIXTURES_FOLDER_NAME);
|
||||||
const PATH_SIGNIN_HTML = path.join(FIXTURES_ROOT, 'startup_css/sign-in.html');
|
const PATH_SIGNIN_HTML = path.join(FIXTURES_ROOT, 'startup_css/sign-in.html');
|
||||||
|
const PATH_SIGNIN_OLD_HTML = path.join(FIXTURES_ROOT, 'startup_css/sign-in-old.html');
|
||||||
const PATH_ASSETS = path.join(ROOT, 'tmp/startup_css_assets');
|
const PATH_ASSETS = path.join(ROOT, 'tmp/startup_css_assets');
|
||||||
const PATH_STARTUP_SCSS = path.join(ROOT_RAILS, 'app/assets/stylesheets/startup');
|
const PATH_STARTUP_SCSS = path.join(ROOT_RAILS, 'app/assets/stylesheets/startup');
|
||||||
|
|
||||||
|
@ -80,7 +81,7 @@ const OUTPUTS = [
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
outFile: 'startup-signin',
|
outFile: 'startup-signin',
|
||||||
htmlPaths: [PATH_SIGNIN_HTML],
|
htmlPaths: [PATH_SIGNIN_HTML, PATH_SIGNIN_OLD_HTML],
|
||||||
cssKeys: [APPLICATION_CSS_PREFIX, UTILITIES_CSS_PREFIX],
|
cssKeys: [APPLICATION_CSS_PREFIX, UTILITIES_CSS_PREFIX],
|
||||||
purgeOptions: {
|
purgeOptions: {
|
||||||
safelist: {
|
safelist: {
|
||||||
|
|
|
@ -32,6 +32,11 @@ RSpec.describe Projects::EnvironmentsController do
|
||||||
|
|
||||||
get :index, params: environment_params
|
get :index, params: environment_params
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'tracking unique visits', :index do
|
||||||
|
let(:request_params) { environment_params }
|
||||||
|
let(:target_id) { 'users_visiting_environments_pages' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when requesting JSON response for folders' do
|
context 'when requesting JSON response for folders' do
|
||||||
|
@ -169,6 +174,18 @@ RSpec.describe Projects::EnvironmentsController do
|
||||||
expect(response).to be_ok
|
expect(response).to be_ok
|
||||||
expect(response).to render_template 'folder'
|
expect(response).to render_template 'folder'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'tracking unique visits', :folder do
|
||||||
|
let(:request_params) do
|
||||||
|
{
|
||||||
|
namespace_id: project.namespace,
|
||||||
|
project_id: project,
|
||||||
|
id: 'staging-1.0'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:target_id) { 'users_visiting_environments_pages' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when using JSON format' do
|
context 'when using JSON format' do
|
||||||
|
@ -197,6 +214,11 @@ RSpec.describe Projects::EnvironmentsController do
|
||||||
|
|
||||||
expect(response).to be_ok
|
expect(response).to be_ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'tracking unique visits', :show do
|
||||||
|
let(:request_params) { environment_params }
|
||||||
|
let(:target_id) { 'users_visiting_environments_pages' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with invalid id' do
|
context 'with invalid id' do
|
||||||
|
@ -210,12 +232,30 @@ RSpec.describe Projects::EnvironmentsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET new' do
|
||||||
|
it 'responds with a status code 200' do
|
||||||
|
get :new, params: environment_params
|
||||||
|
|
||||||
|
expect(response).to be_ok
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'tracking unique visits', :new do
|
||||||
|
let(:request_params) { environment_params }
|
||||||
|
let(:target_id) { 'users_visiting_environments_pages' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'GET edit' do
|
describe 'GET edit' do
|
||||||
it 'responds with a status code 200' do
|
it 'responds with a status code 200' do
|
||||||
get :edit, params: environment_params
|
get :edit, params: environment_params
|
||||||
|
|
||||||
expect(response).to be_ok
|
expect(response).to be_ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'tracking unique visits', :edit do
|
||||||
|
let(:request_params) { environment_params }
|
||||||
|
let(:target_id) { 'users_visiting_environments_pages' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'PATCH #update' do
|
describe 'PATCH #update' do
|
||||||
|
@ -230,6 +270,11 @@ RSpec.describe Projects::EnvironmentsController do
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
expect(json_response['path']).to eq("/#{project.full_path}/-/environments/#{environment.id}")
|
expect(json_response['path']).to eq("/#{project.full_path}/-/environments/#{environment.id}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'tracking unique visits', :update do
|
||||||
|
let(:request_params) { params }
|
||||||
|
let(:target_id) { 'users_visiting_environments_pages' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when environment params are invalid" do
|
context "when environment params are invalid" do
|
||||||
|
@ -294,6 +339,11 @@ RSpec.describe Projects::EnvironmentsController do
|
||||||
{ 'redirect_url' =>
|
{ 'redirect_url' =>
|
||||||
project_environment_url(project, environment) })
|
project_environment_url(project, environment) })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'tracking unique visits', :stop do
|
||||||
|
let(:request_params) { environment_params(format: :json) }
|
||||||
|
let(:target_id) { 'users_visiting_environments_pages' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no stop action' do
|
context 'when no stop action' do
|
||||||
|
@ -321,6 +371,11 @@ RSpec.describe Projects::EnvironmentsController do
|
||||||
|
|
||||||
it_behaves_like 'successful response for #cancel_auto_stop'
|
it_behaves_like 'successful response for #cancel_auto_stop'
|
||||||
|
|
||||||
|
it_behaves_like 'tracking unique visits', :cancel_auto_stop do
|
||||||
|
let(:request_params) { environment_params }
|
||||||
|
let(:target_id) { 'users_visiting_environments_pages' }
|
||||||
|
end
|
||||||
|
|
||||||
context 'when user is reporter' do
|
context 'when user is reporter' do
|
||||||
let(:user) { reporter }
|
let(:user) { reporter }
|
||||||
|
|
||||||
|
@ -357,6 +412,11 @@ RSpec.describe Projects::EnvironmentsController do
|
||||||
|
|
||||||
get :terminal, params: environment_params
|
get :terminal, params: environment_params
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'tracking unique visits', :terminal do
|
||||||
|
let(:request_params) { environment_params }
|
||||||
|
let(:target_id) { 'users_visiting_environments_pages' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with invalid id' do
|
context 'with invalid id' do
|
||||||
|
@ -859,6 +919,11 @@ RSpec.describe Projects::EnvironmentsController do
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
expect(json_response['path']).to eq("/#{project.full_path}/-/environments/#{json_response['environment']['id']}")
|
expect(json_response['path']).to eq("/#{project.full_path}/-/environments/#{json_response['environment']['id']}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'tracking unique visits', :create do
|
||||||
|
let(:request_params) { params }
|
||||||
|
let(:target_id) { 'users_visiting_environments_pages' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when environment params are invalid" do
|
context "when environment params are invalid" do
|
||||||
|
|
|
@ -69,11 +69,25 @@ RSpec.describe 'Startup CSS fixtures', type: :controller do
|
||||||
it_behaves_like 'startup css project fixtures', 'dark'
|
it_behaves_like 'startup css project fixtures', 'dark'
|
||||||
end
|
end
|
||||||
|
|
||||||
describe RegistrationsController, '(Startup CSS fixtures)', type: :controller do
|
describe SessionsController, '(Startup CSS fixtures)', type: :controller do
|
||||||
|
include DeviseHelpers
|
||||||
|
|
||||||
|
before do
|
||||||
|
set_devise_mapping(context: request)
|
||||||
|
end
|
||||||
|
|
||||||
it 'startup_css/sign-in.html' do
|
it 'startup_css/sign-in.html' do
|
||||||
get :new
|
get :new
|
||||||
|
|
||||||
expect(response).to be_successful
|
expect(response).to be_successful
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'startup_css/sign-in-old.html' do
|
||||||
|
stub_feature_flags(restyle_login_page: false)
|
||||||
|
|
||||||
|
get :new
|
||||||
|
|
||||||
|
expect(response).to be_successful
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,11 +4,20 @@ import TopNavMenuSections from '~/nav/components/top_nav_menu_sections.vue';
|
||||||
const TEST_SECTIONS = [
|
const TEST_SECTIONS = [
|
||||||
{
|
{
|
||||||
id: 'primary',
|
id: 'primary',
|
||||||
menuItems: [{ id: 'test', href: '/test/href' }, { id: 'foo' }, { id: 'bar' }],
|
menuItems: [
|
||||||
|
{ type: 'header', title: 'Heading' },
|
||||||
|
{ type: 'item', id: 'test', href: '/test/href' },
|
||||||
|
{ type: 'header', title: 'Another Heading' },
|
||||||
|
{ type: 'item', id: 'foo' },
|
||||||
|
{ type: 'item', id: 'bar' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'secondary',
|
id: 'secondary',
|
||||||
menuItems: [{ id: 'lorem' }, { id: 'ipsum' }],
|
menuItems: [
|
||||||
|
{ type: 'item', id: 'lorem' },
|
||||||
|
{ type: 'item', id: 'ipsum' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -25,10 +34,20 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const findMenuItemModels = (parent) =>
|
const findMenuItemModels = (parent) =>
|
||||||
parent.findAll('[data-testid="menu-item"]').wrappers.map((x) => ({
|
parent.findAll('[data-testid="menu-header"],[data-testid="menu-item"]').wrappers.map((x) => {
|
||||||
menuItem: x.props('menuItem'),
|
return {
|
||||||
classes: x.classes(),
|
menuItem: x.vm
|
||||||
}));
|
? {
|
||||||
|
type: 'item',
|
||||||
|
...x.props('menuItem'),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'header',
|
||||||
|
title: x.text(),
|
||||||
|
},
|
||||||
|
classes: x.classes(),
|
||||||
|
};
|
||||||
|
});
|
||||||
const findSectionModels = () =>
|
const findSectionModels = () =>
|
||||||
wrapper.findAll('[data-testid="menu-section"]').wrappers.map((x) => ({
|
wrapper.findAll('[data-testid="menu-section"]').wrappers.map((x) => ({
|
||||||
classes: x.classes(),
|
classes: x.classes(),
|
||||||
|
@ -45,32 +64,31 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders sections with menu items', () => {
|
it('renders sections with menu items', () => {
|
||||||
|
const headerClasses = ['gl-px-4', 'gl-py-2', 'gl-text-gray-900', 'gl-display-block'];
|
||||||
|
const itemClasses = ['gl-w-full'];
|
||||||
|
|
||||||
expect(findSectionModels()).toEqual([
|
expect(findSectionModels()).toEqual([
|
||||||
{
|
{
|
||||||
classes: [],
|
classes: [],
|
||||||
menuItems: [
|
menuItems: TEST_SECTIONS[0].menuItems.map((menuItem, index) => {
|
||||||
{
|
const classes = menuItem.type === 'header' ? [...headerClasses] : [...itemClasses];
|
||||||
menuItem: TEST_SECTIONS[0].menuItems[0],
|
if (index > 0) classes.push(menuItem.type === 'header' ? 'gl-pt-3!' : 'gl-mt-1');
|
||||||
classes: ['gl-w-full'],
|
return {
|
||||||
},
|
|
||||||
...TEST_SECTIONS[0].menuItems.slice(1).map((menuItem) => ({
|
|
||||||
menuItem,
|
menuItem,
|
||||||
classes: ['gl-w-full', 'gl-mt-1'],
|
classes,
|
||||||
})),
|
};
|
||||||
],
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
classes: [...TopNavMenuSections.BORDER_CLASSES.split(' '), 'gl-mt-3'],
|
classes: [...TopNavMenuSections.BORDER_CLASSES.split(' '), 'gl-mt-3'],
|
||||||
menuItems: [
|
menuItems: TEST_SECTIONS[1].menuItems.map((menuItem, index) => {
|
||||||
{
|
const classes = menuItem.type === 'header' ? [...headerClasses] : [...itemClasses];
|
||||||
menuItem: TEST_SECTIONS[1].menuItems[0],
|
if (index > 0) classes.push(menuItem.type === 'header' ? 'gl-pt-3!' : 'gl-mt-1');
|
||||||
classes: ['gl-w-full'],
|
return {
|
||||||
},
|
|
||||||
...TEST_SECTIONS[1].menuItems.slice(1).map((menuItem) => ({
|
|
||||||
menuItem,
|
menuItem,
|
||||||
classes: ['gl-w-full', 'gl-mt-1'],
|
classes,
|
||||||
})),
|
};
|
||||||
],
|
}),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -88,7 +106,7 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
|
||||||
|
|
||||||
menuItem.vm.$emit('click');
|
menuItem.vm.$emit('click');
|
||||||
|
|
||||||
expect(wrapper.emitted('menu-item-click')).toEqual([[TEST_SECTIONS[0].menuItems[1]]]);
|
expect(wrapper.emitted('menu-item-click')).toEqual([[TEST_SECTIONS[0].menuItems[3]]]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { setHTMLFixture, resetHTMLFixture } from 'jest/__helpers__/fixtures';
|
||||||
|
import initFormUpdate from '~/pages/projects/merge_requests/edit/update_form';
|
||||||
|
|
||||||
|
describe('Update form state', () => {
|
||||||
|
const submitEvent = new Event('submit', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const submitForm = () => document.querySelector('.merge-request-form').dispatchEvent(submitEvent);
|
||||||
|
const hiddenInputs = () => document.querySelectorAll('input[type="hidden"]');
|
||||||
|
const checkboxes = () => document.querySelectorAll('.js-form-update');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setHTMLFixture(`
|
||||||
|
<form class="merge-request-form">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="hidden" name="merge_request[force_remove_source_branch]" value="0" autocomplete="off">
|
||||||
|
<input type="checkbox" name="merge_request[force_remove_source_branch]" id="merge_request_force_remove_source_branch" value="1" class="form-check-input js-form-update">
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="hidden" name="merge_request[squash]" value="0" autocomplete="off">
|
||||||
|
<input type="checkbox" name="merge_request[squash]" id="merge_request_squash" value="1" class="form-check-input js-form-update">
|
||||||
|
</div>
|
||||||
|
</form>`);
|
||||||
|
initFormUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
resetHTMLFixture();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('at initial state', () => {
|
||||||
|
submitForm();
|
||||||
|
expect(hiddenInputs()).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when one element is checked', () => {
|
||||||
|
checkboxes()[0].setAttribute('checked', true);
|
||||||
|
submitForm();
|
||||||
|
expect(hiddenInputs()).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when all elements are checked', () => {
|
||||||
|
checkboxes()[0].setAttribute('checked', true);
|
||||||
|
checkboxes()[1].setAttribute('checked', true);
|
||||||
|
submitForm();
|
||||||
|
expect(hiddenInputs()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when checked and then unchecked', () => {
|
||||||
|
checkboxes()[0].setAttribute('checked', true);
|
||||||
|
checkboxes()[0].removeAttribute('checked');
|
||||||
|
checkboxes()[1].setAttribute('checked', true);
|
||||||
|
checkboxes()[1].removeAttribute('checked');
|
||||||
|
submitForm();
|
||||||
|
expect(hiddenInputs()).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,10 +2,12 @@ import { GlFilteredSearch } from '@gitlab/ui';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
|
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||||
import Api from '~/api';
|
import Api from '~/api';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import PipelinesFilteredSearch from '~/pipelines/components/pipelines_list/pipelines_filtered_search.vue';
|
import PipelinesFilteredSearch from '~/pipelines/components/pipelines_list/pipelines_filtered_search.vue';
|
||||||
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
|
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||||
|
import { TRACKING_CATEGORIES } from '~/pipelines/constants';
|
||||||
import { users, mockSearch, branches, tags } from '../mock_data';
|
import { users, mockSearch, branches, tags } from '../mock_data';
|
||||||
|
|
||||||
describe('Pipelines filtered search', () => {
|
describe('Pipelines filtered search', () => {
|
||||||
|
@ -177,4 +179,20 @@ describe('Pipelines filtered search', () => {
|
||||||
expect(findFilteredSearch().props('value')).toHaveLength(expectedValueProp.length);
|
expect(findFilteredSearch().props('value')).toHaveLength(expectedValueProp.length);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('tracking', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
unmockTracking();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks filtered search click', () => {
|
||||||
|
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||||
|
|
||||||
|
findFilteredSearch().vm.$emit('submit', mockSearch);
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_filtered_search', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { GlAlert, GlDropdown, GlSprintf, GlLoadingIcon } from '@gitlab/ui';
|
import { GlAlert, GlDropdown, GlSprintf, GlLoadingIcon } from '@gitlab/ui';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import PipelineMultiActions, {
|
import PipelineMultiActions, {
|
||||||
i18n,
|
i18n,
|
||||||
} from '~/pipelines/components/pipelines_list/pipeline_multi_actions.vue';
|
} from '~/pipelines/components/pipelines_list/pipeline_multi_actions.vue';
|
||||||
|
import { TRACKING_CATEGORIES } from '~/pipelines/constants';
|
||||||
|
|
||||||
describe('Pipeline Multi Actions Dropdown', () => {
|
describe('Pipeline Multi Actions Dropdown', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
@ -136,4 +138,22 @@ describe('Pipeline Multi Actions Dropdown', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('tracking', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
unmockTracking();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks artifacts dropdown click', () => {
|
||||||
|
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||||
|
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
findDropdown().vm.$emit('show');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_artifacts_dropdown', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
|
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
|
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
|
||||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||||
|
import { TRACKING_CATEGORIES } from '~/pipelines/constants';
|
||||||
import { mockPipeline, mockPipelineBranch, mockPipelineTag } from './mock_data';
|
import { mockPipeline, mockPipelineBranch, mockPipelineTag } from './mock_data';
|
||||||
|
|
||||||
const projectPath = 'test/test';
|
const projectPath = 'test/test';
|
||||||
|
|
||||||
describe('Pipeline Url Component', () => {
|
describe('Pipeline Url Component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let trackingSpy;
|
||||||
|
|
||||||
const findTableCell = () => wrapper.findByTestId('pipeline-url-table-cell');
|
const findTableCell = () => wrapper.findByTestId('pipeline-url-table-cell');
|
||||||
const findPipelineUrlLink = () => wrapper.findByTestId('pipeline-url-link');
|
const findPipelineUrlLink = () => wrapper.findByTestId('pipeline-url-link');
|
||||||
|
@ -14,6 +17,7 @@ describe('Pipeline Url Component', () => {
|
||||||
const findCommitShortSha = () => wrapper.findByTestId('commit-short-sha');
|
const findCommitShortSha = () => wrapper.findByTestId('commit-short-sha');
|
||||||
const findCommitIcon = () => wrapper.findByTestId('commit-icon');
|
const findCommitIcon = () => wrapper.findByTestId('commit-icon');
|
||||||
const findCommitIconType = () => wrapper.findByTestId('commit-icon-type');
|
const findCommitIconType = () => wrapper.findByTestId('commit-icon-type');
|
||||||
|
const findCommitRefName = () => wrapper.findByTestId('commit-ref-name');
|
||||||
|
|
||||||
const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container');
|
const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container');
|
||||||
const findCommitTitle = (commitWrapper) => commitWrapper.find('[data-testid="commit-title"]');
|
const findCommitTitle = (commitWrapper) => commitWrapper.find('[data-testid="commit-title"]');
|
||||||
|
@ -31,7 +35,6 @@ describe('Pipeline Url Component', () => {
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
wrapper = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render pipeline url table cell', () => {
|
it('should render pipeline url table cell', () => {
|
||||||
|
@ -49,7 +52,7 @@ describe('Pipeline Url Component', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render the commit title, commit reference and commit-short-sha', () => {
|
it('should render the commit title, commit reference and commit-short-sha', () => {
|
||||||
createComponent({}, true);
|
createComponent();
|
||||||
|
|
||||||
const commitWrapper = findCommitTitleContainer();
|
const commitWrapper = findCommitTitleContainer();
|
||||||
|
|
||||||
|
@ -83,7 +86,7 @@ describe('Pipeline Url Component', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render commit icon tooltip', () => {
|
it('should render commit icon tooltip', () => {
|
||||||
createComponent({}, true);
|
createComponent();
|
||||||
|
|
||||||
expect(findCommitIcon().attributes('title')).toBe('Commit');
|
expect(findCommitIcon().attributes('title')).toBe('Commit');
|
||||||
});
|
});
|
||||||
|
@ -94,8 +97,68 @@ describe('Pipeline Url Component', () => {
|
||||||
${mockPipelineBranch()} | ${'Branch'}
|
${mockPipelineBranch()} | ${'Branch'}
|
||||||
${mockPipeline()} | ${'Merge Request'}
|
${mockPipeline()} | ${'Merge Request'}
|
||||||
`('should render tooltip $expectedTitle for commit icon type', ({ pipeline, expectedTitle }) => {
|
`('should render tooltip $expectedTitle for commit icon type', ({ pipeline, expectedTitle }) => {
|
||||||
createComponent(pipeline, true);
|
createComponent(pipeline);
|
||||||
|
|
||||||
expect(findCommitIconType().attributes('title')).toBe(expectedTitle);
|
expect(findCommitIconType().attributes('title')).toBe(expectedTitle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('tracking', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
unmockTracking();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks pipeline id click', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
findPipelineUrlLink().vm.$emit('click');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_pipeline_id', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks merge request ref click', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
findRefName().vm.$emit('click');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_mr_ref', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks commit ref name click', () => {
|
||||||
|
createComponent(mockPipelineBranch());
|
||||||
|
|
||||||
|
findCommitRefName().vm.$emit('click');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_commit_name', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks commit title click', () => {
|
||||||
|
createComponent(mockPipelineBranch());
|
||||||
|
|
||||||
|
findCommitTitle(findCommitTitleContainer()).vm.$emit('click');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_commit_title', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks commit short sha click', () => {
|
||||||
|
createComponent(mockPipelineBranch());
|
||||||
|
|
||||||
|
findCommitShortSha().vm.$emit('click');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_commit_sha', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
|
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
import { TEST_HOST } from 'spec/test_constants';
|
import { TEST_HOST } from 'spec/test_constants';
|
||||||
import createFlash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
|
@ -9,6 +10,7 @@ import axios from '~/lib/utils/axios_utils';
|
||||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||||
import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue';
|
import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue';
|
||||||
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
|
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
|
||||||
|
import { TRACKING_CATEGORIES } from '~/pipelines/constants';
|
||||||
|
|
||||||
jest.mock('~/flash');
|
jest.mock('~/flash');
|
||||||
jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal', () => {
|
jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal', () => {
|
||||||
|
@ -96,6 +98,22 @@ describe('Pipelines Actions dropdown', () => {
|
||||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
expect(createFlash).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('tracking', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
unmockTracking();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks manual actions click', () => {
|
||||||
|
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||||
|
|
||||||
|
findDropdown().vm.$emit('shown');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_manual_actions', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('scheduled jobs', () => {
|
describe('scheduled jobs', () => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import '~/commons';
|
||||||
import { GlTableLite } from '@gitlab/ui';
|
import { GlTableLite } from '@gitlab/ui';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import fixture from 'test_fixtures/pipelines/pipelines.json';
|
import fixture from 'test_fixtures/pipelines/pipelines.json';
|
||||||
|
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||||
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
|
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
|
||||||
import PipelineOperations from '~/pipelines/components/pipelines_list/pipeline_operations.vue';
|
import PipelineOperations from '~/pipelines/components/pipelines_list/pipeline_operations.vue';
|
||||||
|
@ -13,6 +14,7 @@ import {
|
||||||
PipelineKeyOptions,
|
PipelineKeyOptions,
|
||||||
BUTTON_TOOLTIP_RETRY,
|
BUTTON_TOOLTIP_RETRY,
|
||||||
BUTTON_TOOLTIP_CANCEL,
|
BUTTON_TOOLTIP_CANCEL,
|
||||||
|
TRACKING_CATEGORIES,
|
||||||
} from '~/pipelines/constants';
|
} from '~/pipelines/constants';
|
||||||
|
|
||||||
import eventHub from '~/pipelines/event_hub';
|
import eventHub from '~/pipelines/event_hub';
|
||||||
|
@ -23,6 +25,7 @@ jest.mock('~/pipelines/event_hub');
|
||||||
describe('Pipelines Table', () => {
|
describe('Pipelines Table', () => {
|
||||||
let pipeline;
|
let pipeline;
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let trackingSpy;
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
pipelines: [],
|
pipelines: [],
|
||||||
|
@ -69,6 +72,7 @@ describe('Pipelines Table', () => {
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
|
|
||||||
wrapper = null;
|
wrapper = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -96,10 +100,6 @@ describe('Pipelines Table', () => {
|
||||||
it('should render a status badge', () => {
|
it('should render a status badge', () => {
|
||||||
expect(findStatusBadge().exists()).toBe(true);
|
expect(findStatusBadge().exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render status badge with correct path', () => {
|
|
||||||
expect(findStatusBadge().attributes('href')).toBe(pipeline.path);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('pipeline cell', () => {
|
describe('pipeline cell', () => {
|
||||||
|
@ -167,5 +167,39 @@ describe('Pipelines Table', () => {
|
||||||
expect(findTriggerer().exists()).toBe(true);
|
expect(findTriggerer().exists()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('tracking', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
unmockTracking();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks status badge click', () => {
|
||||||
|
findStatusBadge().vm.$emit('ciStatusBadgeClick');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_ci_status_badge', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks retry pipeline button click', () => {
|
||||||
|
findRetryBtn().vm.$emit('click');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_retry_button', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks cancel pipeline button click', () => {
|
||||||
|
findCancelBtn().vm.$emit('click');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_cancel_button', {
|
||||||
|
label: TRACKING_CATEGORIES.index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
|
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
|
||||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||||
|
import { visitUrl } from '~/lib/utils/url_utility';
|
||||||
|
|
||||||
|
jest.mock('~/lib/utils/url_utility', () => ({
|
||||||
|
visitUrl: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('CI Badge Link Component', () => {
|
describe('CI Badge Link Component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
@ -79,17 +84,20 @@ describe('CI Badge Link Component', () => {
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
wrapper = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each(Object.keys(statuses))('should render badge for status: %s', (status) => {
|
it.each(Object.keys(statuses))('should render badge for status: %s', async (status) => {
|
||||||
createComponent({ status: statuses[status] });
|
createComponent({ status: statuses[status] });
|
||||||
|
|
||||||
expect(wrapper.attributes('href')).toBe(statuses[status].details_path);
|
expect(wrapper.attributes('href')).toBe();
|
||||||
expect(wrapper.text()).toBe(statuses[status].text);
|
expect(wrapper.text()).toBe(statuses[status].text);
|
||||||
expect(wrapper.classes()).toContain('ci-status');
|
expect(wrapper.classes()).toContain('ci-status');
|
||||||
expect(wrapper.classes()).toContain(`ci-${statuses[status].group}`);
|
expect(wrapper.classes()).toContain(`ci-${statuses[status].group}`);
|
||||||
expect(findIcon().exists()).toBe(true);
|
expect(findIcon().exists()).toBe(true);
|
||||||
|
|
||||||
|
await wrapper.trigger('click');
|
||||||
|
|
||||||
|
expect(visitUrl).toHaveBeenCalledWith(statuses[status].details_path);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not render label', () => {
|
it('should not render label', () => {
|
||||||
|
@ -97,4 +105,12 @@ describe('CI Badge Link Component', () => {
|
||||||
|
|
||||||
expect(wrapper.text()).toBe('');
|
expect(wrapper.text()).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should emit ciStatusBadgeClick event', async () => {
|
||||||
|
createComponent({ status: statuses.success });
|
||||||
|
|
||||||
|
await wrapper.trigger('click');
|
||||||
|
|
||||||
|
expect(wrapper.emitted('ciStatusBadgeClick')).toEqual([[]]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,6 +52,9 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
|
|
||||||
context 'when current_user is nil (anonymous)' do
|
context 'when current_user is nil (anonymous)' do
|
||||||
it 'has expected :primary' do
|
it 'has expected :primary' do
|
||||||
|
expected_header = ::Gitlab::Nav::TopNavMenuHeader.build(
|
||||||
|
title: 'Explore'
|
||||||
|
)
|
||||||
expected_primary = [
|
expected_primary = [
|
||||||
{ href: '/explore', icon: 'project', id: 'project', title: 'Projects' },
|
{ href: '/explore', icon: 'project', id: 'project', title: 'Projects' },
|
||||||
{ href: '/explore/groups', icon: 'group', id: 'groups', title: 'Groups' },
|
{ href: '/explore/groups', icon: 'group', id: 'groups', title: 'Groups' },
|
||||||
|
@ -60,7 +63,7 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
::Gitlab::Nav::TopNavMenuItem.build(**item)
|
::Gitlab::Nav::TopNavMenuItem.build(**item)
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(subject[:primary]).to eq(expected_primary)
|
expect(subject[:primary]).to eq([expected_header, *expected_primary])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has expected :shortcuts' do
|
it 'has expected :shortcuts' do
|
||||||
|
@ -117,6 +120,9 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
let(:projects_view) { subject[:views][:projects] }
|
let(:projects_view) { subject[:views][:projects] }
|
||||||
|
|
||||||
it 'has expected :primary' do
|
it 'has expected :primary' do
|
||||||
|
expected_header = ::Gitlab::Nav::TopNavMenuHeader.build(
|
||||||
|
title: 'Switch to'
|
||||||
|
)
|
||||||
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
|
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
|
||||||
css_class: 'qa-projects-dropdown',
|
css_class: 'qa-projects-dropdown',
|
||||||
data: {
|
data: {
|
||||||
|
@ -128,7 +134,7 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
title: 'Projects',
|
title: 'Projects',
|
||||||
view: 'projects'
|
view: 'projects'
|
||||||
)
|
)
|
||||||
expect(subject[:primary]).to eq([expected_primary])
|
expect(subject[:primary]).to eq([expected_header, expected_primary])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has expected :shortcuts' do
|
it 'has expected :shortcuts' do
|
||||||
|
@ -253,6 +259,9 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
let(:groups_view) { subject[:views][:groups] }
|
let(:groups_view) { subject[:views][:groups] }
|
||||||
|
|
||||||
it 'has expected :primary' do
|
it 'has expected :primary' do
|
||||||
|
expected_header = ::Gitlab::Nav::TopNavMenuHeader.build(
|
||||||
|
title: 'Switch to'
|
||||||
|
)
|
||||||
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
|
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
|
||||||
css_class: 'qa-groups-dropdown',
|
css_class: 'qa-groups-dropdown',
|
||||||
data: {
|
data: {
|
||||||
|
@ -264,7 +273,7 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
title: 'Groups',
|
title: 'Groups',
|
||||||
view: 'groups'
|
view: 'groups'
|
||||||
)
|
)
|
||||||
expect(subject[:primary]).to eq([expected_primary])
|
expect(subject[:primary]).to eq([expected_header, expected_primary])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has expected :shortcuts' do
|
it 'has expected :shortcuts' do
|
||||||
|
@ -376,6 +385,9 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
let(:with_milestones) { true }
|
let(:with_milestones) { true }
|
||||||
|
|
||||||
it 'has expected :primary' do
|
it 'has expected :primary' do
|
||||||
|
expected_header = ::Gitlab::Nav::TopNavMenuHeader.build(
|
||||||
|
title: 'Explore'
|
||||||
|
)
|
||||||
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
|
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
|
||||||
data: {
|
data: {
|
||||||
qa_selector: 'milestones_link',
|
qa_selector: 'milestones_link',
|
||||||
|
@ -386,7 +398,7 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
id: 'milestones',
|
id: 'milestones',
|
||||||
title: 'Milestones'
|
title: 'Milestones'
|
||||||
)
|
)
|
||||||
expect(subject[:primary]).to eq([expected_primary])
|
expect(subject[:primary]).to eq([expected_header, expected_primary])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has expected :shortcuts' do
|
it 'has expected :shortcuts' do
|
||||||
|
@ -404,6 +416,9 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
let(:with_snippets) { true }
|
let(:with_snippets) { true }
|
||||||
|
|
||||||
it 'has expected :primary' do
|
it 'has expected :primary' do
|
||||||
|
expected_header = ::Gitlab::Nav::TopNavMenuHeader.build(
|
||||||
|
title: 'Explore'
|
||||||
|
)
|
||||||
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
|
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
|
||||||
data: {
|
data: {
|
||||||
qa_selector: 'snippets_link',
|
qa_selector: 'snippets_link',
|
||||||
|
@ -414,7 +429,7 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
id: 'snippets',
|
id: 'snippets',
|
||||||
title: 'Snippets'
|
title: 'Snippets'
|
||||||
)
|
)
|
||||||
expect(subject[:primary]).to eq([expected_primary])
|
expect(subject[:primary]).to eq([expected_header, expected_primary])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has expected :shortcuts' do
|
it 'has expected :shortcuts' do
|
||||||
|
@ -432,6 +447,9 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
let(:with_activity) { true }
|
let(:with_activity) { true }
|
||||||
|
|
||||||
it 'has expected :primary' do
|
it 'has expected :primary' do
|
||||||
|
expected_header = ::Gitlab::Nav::TopNavMenuHeader.build(
|
||||||
|
title: 'Explore'
|
||||||
|
)
|
||||||
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
|
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
|
||||||
data: {
|
data: {
|
||||||
qa_selector: 'activity_link',
|
qa_selector: 'activity_link',
|
||||||
|
@ -442,7 +460,7 @@ RSpec.describe Nav::TopNavHelper do
|
||||||
id: 'activity',
|
id: 'activity',
|
||||||
title: 'Activity'
|
title: 'Activity'
|
||||||
)
|
)
|
||||||
expect(subject[:primary]).to eq([expected_primary])
|
expect(subject[:primary]).to eq([expected_header, expected_primary])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has expected :shortcuts' do
|
it 'has expected :shortcuts' do
|
||||||
|
|
|
@ -30,7 +30,7 @@ RSpec.describe Projects::PipelineHelper do
|
||||||
summary_endpoint: summary_project_pipeline_tests_path(project, pipeline, format: :json),
|
summary_endpoint: summary_project_pipeline_tests_path(project, pipeline, format: :json),
|
||||||
suite_endpoint: project_pipeline_test_path(project, pipeline, suite_name: 'suite', format: :json),
|
suite_endpoint: project_pipeline_test_path(project, pipeline, suite_name: 'suite', format: :json),
|
||||||
blob_path: project_blob_path(project, pipeline.sha),
|
blob_path: project_blob_path(project, pipeline.sha),
|
||||||
has_test_report: pipeline.has_reports?(Ci::JobArtifact.of_report_type(:test)),
|
has_test_report: pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:test)),
|
||||||
empty_state_image_path: match_asset_path('illustrations/empty-state/empty-test-cases-lg.svg'),
|
empty_state_image_path: match_asset_path('illustrations/empty-state/empty-test-cases-lg.svg'),
|
||||||
artifacts_expired_image_path: match_asset_path('illustrations/pipeline.svg'),
|
artifacts_expired_image_path: match_asset_path('illustrations/pipeline.svg'),
|
||||||
tests_count: pipeline.test_report_summary.total[:count]
|
tests_count: pipeline.test_report_summary.total[:count]
|
||||||
|
|
|
@ -61,8 +61,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::LegacyVariables do
|
||||||
describe '#value_with_data' do
|
describe '#value_with_data' do
|
||||||
it 'returns variable with data' do
|
it 'returns variable with data' do
|
||||||
expect(entry.value_with_data).to eq(
|
expect(entry.value_with_data).to eq(
|
||||||
'VARIABLE_1' => { value: 'value 1', description: nil },
|
'VARIABLE_1' => { value: 'value 1' },
|
||||||
'VARIABLE_2' => { value: 'value 2', description: nil }
|
'VARIABLE_2' => { value: 'value 2' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -125,7 +125,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::LegacyVariables do
|
||||||
it 'returns variable with data' do
|
it 'returns variable with data' do
|
||||||
expect(entry.value_with_data).to eq(
|
expect(entry.value_with_data).to eq(
|
||||||
'VARIABLE_1' => { value: 'value 1', description: 'variable 1' },
|
'VARIABLE_1' => { value: 'value 1', description: 'variable 1' },
|
||||||
'VARIABLE_2' => { value: 'value 2', description: nil }
|
'VARIABLE_2' => { value: 'value 2' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -204,7 +204,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variable do
|
||||||
describe '#value_with_data' do
|
describe '#value_with_data' do
|
||||||
subject(:value_with_data) { entry.value_with_data }
|
subject(:value_with_data) { entry.value_with_data }
|
||||||
|
|
||||||
it { is_expected.to eq(value: 'value', description: nil) }
|
it { is_expected.to eq(value: 'value') }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -61,8 +61,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do
|
||||||
describe '#value_with_data' do
|
describe '#value_with_data' do
|
||||||
it 'returns variable with data' do
|
it 'returns variable with data' do
|
||||||
expect(entry.value_with_data).to eq(
|
expect(entry.value_with_data).to eq(
|
||||||
'VARIABLE_1' => { value: 'value 1', description: nil },
|
'VARIABLE_1' => { value: 'value 1' },
|
||||||
'VARIABLE_2' => { value: 'value 2', description: nil }
|
'VARIABLE_2' => { value: 'value 2' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -119,7 +119,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do
|
||||||
it 'returns variable with data' do
|
it 'returns variable with data' do
|
||||||
expect(entry.value_with_data).to eq(
|
expect(entry.value_with_data).to eq(
|
||||||
'VARIABLE_1' => { value: 'value 1', description: 'variable 1' },
|
'VARIABLE_1' => { value: 'value 1', description: 'variable 1' },
|
||||||
'VARIABLE_2' => { value: 'value 2', description: nil }
|
'VARIABLE_2' => { value: 'value 2' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::Ci::Pipeline::Chain::AssignPartition do
|
||||||
|
let_it_be(:project) { create(:project) }
|
||||||
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
|
let(:command) do
|
||||||
|
Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:pipeline) { build(:ci_pipeline, project: project) }
|
||||||
|
let(:step) { described_class.new(pipeline, command) }
|
||||||
|
let(:current_partition_id) { 123 }
|
||||||
|
|
||||||
|
describe '#perform!' do
|
||||||
|
before do
|
||||||
|
stub_const("#{described_class}::DEFAULT_PARTITION_ID", current_partition_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { step.perform! }
|
||||||
|
|
||||||
|
it 'assigns partition_id to pipeline' do
|
||||||
|
expect { subject }.to change(pipeline, :partition_id).to(current_partition_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -630,7 +630,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
|
||||||
describe '#progress' do
|
describe '#progress' do
|
||||||
subject { migration.progress }
|
subject { migration.progress }
|
||||||
|
|
||||||
context 'when the migration is finished' do
|
context 'when the migration is completed' do
|
||||||
let(:migration) do
|
let(:migration) do
|
||||||
create(:batched_background_migration, :finished, total_tuple_count: 1).tap do |record|
|
create(:batched_background_migration, :finished, total_tuple_count: 1).tap do |record|
|
||||||
create(:batched_background_migration_job, :succeeded, batched_migration: record, batch_size: 1)
|
create(:batched_background_migration_job, :succeeded, batched_migration: record, batch_size: 1)
|
||||||
|
@ -642,6 +642,18 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the status is finished' do
|
||||||
|
let(:migration) do
|
||||||
|
create(:batched_background_migration, :finished, total_tuple_count: 100).tap do |record|
|
||||||
|
create(:batched_background_migration_job, :succeeded, batched_migration: record, batch_size: 5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 100' do
|
||||||
|
expect(subject).to be 100
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the migration does not have jobs' do
|
context 'when the migration does not have jobs' do
|
||||||
let(:migration) { create(:batched_background_migration, :active) }
|
let(:migration) { create(:batched_background_migration, :active) }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'fast_spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe ::Gitlab::Nav::TopNavMenuHeader do
|
||||||
|
describe '.build' do
|
||||||
|
it 'builds a hash from with the given header' do
|
||||||
|
title = 'Test Header'
|
||||||
|
expected = {
|
||||||
|
title: title,
|
||||||
|
type: :header
|
||||||
|
}
|
||||||
|
expect(described_class.build(title: title)).to eq(expected)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,7 +17,7 @@ RSpec.describe ::Gitlab::Nav::TopNavMenuItem do
|
||||||
emoji: 'smile'
|
emoji: 'smile'
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(described_class.build(**item)).to eq(item)
|
expect(described_class.build(**item)).to eq(item.merge(type: :item))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4211,8 +4211,8 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#has_reports?' do
|
describe '#complete_and_has_reports?' do
|
||||||
subject { pipeline.has_reports?(Ci::JobArtifact.of_report_type(:test)) }
|
subject { pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:test)) }
|
||||||
|
|
||||||
context 'when pipeline has builds with test reports' do
|
context 'when pipeline has builds with test reports' do
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -75,4 +75,56 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'PUT /admin/batched_background_migrations/:id/resume' do
|
||||||
|
let!(:migration) { create(:batched_background_migration, :paused) }
|
||||||
|
let(:database) { :main }
|
||||||
|
|
||||||
|
subject(:resume) do
|
||||||
|
put api("/admin/batched_background_migrations/#{migration.id}/resume", admin), params: { database: database }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'pauses the batched background migration' do
|
||||||
|
resume
|
||||||
|
|
||||||
|
aggregate_failures "testing response" do
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(json_response['id']).to eq(migration.id)
|
||||||
|
expect(json_response['status']).to eq('active')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the batched background migration does not exist' do
|
||||||
|
let(:params) { { database: database } }
|
||||||
|
|
||||||
|
it 'returns 404' do
|
||||||
|
put api("/admin/batched_background_migrations/#{non_existing_record_id}/resume", admin), params: params
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when multiple database is enabled' do
|
||||||
|
let(:ci_model) { Ci::ApplicationRecord }
|
||||||
|
let(:database) { :ci }
|
||||||
|
|
||||||
|
before do
|
||||||
|
skip_if_multiple_databases_not_setup
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'uses the correct connection' do
|
||||||
|
expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(ci_model.connection).and_yield
|
||||||
|
|
||||||
|
resume
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when authenticated as a non-admin user' do
|
||||||
|
it 'returns 403' do
|
||||||
|
put api("/admin/batched_background_migrations/#{migration.id}/resume", unauthorized_user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, :aggregate_failures do
|
||||||
|
let_it_be(:project) { create(:project, :repository) }
|
||||||
|
let_it_be(:user) { project.first_owner }
|
||||||
|
|
||||||
|
let(:service) { described_class.new(project, user, { ref: 'master' }) }
|
||||||
|
let(:config) do
|
||||||
|
<<-YAML
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
script: make build
|
||||||
|
|
||||||
|
test:
|
||||||
|
stage: test
|
||||||
|
trigger:
|
||||||
|
include: child.yml
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
stage: deploy
|
||||||
|
script: make deploy
|
||||||
|
environment: review/$CI_JOB_NAME
|
||||||
|
YAML
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:pipeline) { service.execute(:push).payload }
|
||||||
|
let(:current_partition_id) { 123 }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_ci_pipeline_yaml_file(config)
|
||||||
|
stub_const(
|
||||||
|
'Gitlab::Ci::Pipeline::Chain::AssignPartition::DEFAULT_PARTITION_ID',
|
||||||
|
current_partition_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'assigns partition_id to pipeline' do
|
||||||
|
expect(pipeline).to be_created_successfully
|
||||||
|
expect(pipeline.partition_id).to eq(current_partition_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'assigns partition_id to stages' do
|
||||||
|
stage_partition_ids = pipeline.stages.map(&:partition_id).uniq
|
||||||
|
|
||||||
|
expect(stage_partition_ids).to eq([current_partition_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'assigns partition_id to processables' do
|
||||||
|
processables_partition_ids = pipeline.processables.map(&:partition_id).uniq
|
||||||
|
|
||||||
|
expect(processables_partition_ids).to eq([current_partition_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'assigns partition_id to metadata' do
|
||||||
|
metadata_partition_ids = pipeline.processables.map { |job| job.metadata.partition_id }.uniq
|
||||||
|
|
||||||
|
expect(metadata_partition_ids).to eq([current_partition_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'correctly assigns partition and environment' do
|
||||||
|
metadata = find_metadata('deploy')
|
||||||
|
|
||||||
|
expect(metadata.partition_id).to eq(current_partition_id)
|
||||||
|
expect(metadata.expanded_environment_name).to eq('review/deploy')
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_metadata(name)
|
||||||
|
pipeline
|
||||||
|
.processables
|
||||||
|
.find { |job| job.name == name }
|
||||||
|
.metadata
|
||||||
|
end
|
||||||
|
end
|
|
@ -40,8 +40,8 @@ RSpec.describe Ci::ListConfigVariablesService, :use_clean_rails_memory_store_cac
|
||||||
it 'returns variable list' do
|
it 'returns variable list' do
|
||||||
expect(subject['KEY1']).to eq({ value: 'val 1', description: 'description 1' })
|
expect(subject['KEY1']).to eq({ value: 'val 1', description: 'description 1' })
|
||||||
expect(subject['KEY2']).to eq({ value: 'val 2', description: '' })
|
expect(subject['KEY2']).to eq({ value: 'val 2', description: '' })
|
||||||
expect(subject['KEY3']).to eq({ value: 'val 3', description: nil })
|
expect(subject['KEY3']).to eq({ value: 'val 3' })
|
||||||
expect(subject['KEY4']).to eq({ value: 'val 4', description: nil })
|
expect(subject['KEY4']).to eq({ value: 'val 4' })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,14 @@ RSpec.describe Ci::Pipelines::AddJobService do
|
||||||
).and change { job.metadata.project }.to(pipeline.project)
|
).and change { job.metadata.project }.to(pipeline.project)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'assigns partition_id to job and metadata' do
|
||||||
|
pipeline.partition_id = 123
|
||||||
|
|
||||||
|
expect { execute }
|
||||||
|
.to change(job, :partition_id).to(pipeline.partition_id)
|
||||||
|
.and change { job.metadata.partition_id }.to(pipeline.partition_id)
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns a service response with the job as payload' do
|
it 'returns a service response with the job as payload' do
|
||||||
expect(execute).to be_success
|
expect(execute).to be_success
|
||||||
expect(execute.payload[:job]).to eq(job)
|
expect(execute.payload[:job]).to eq(job)
|
||||||
|
|
|
@ -80,13 +80,13 @@ func (u *uploader) Consume(outerCtx context.Context, reader io.Reader, deadline
|
||||||
|
|
||||||
cr := &countReader{r: reader}
|
cr := &countReader{r: reader}
|
||||||
if err := u.strategy.Upload(uploadCtx, cr); err != nil {
|
if err := u.strategy.Upload(uploadCtx, cr); err != nil {
|
||||||
return cr.n, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.checkETag {
|
if u.checkETag {
|
||||||
if err := compareMD5(hexString(hasher), u.strategy.ETag()); err != nil {
|
if err := compareMD5(hexString(hasher), u.strategy.ETag()); err != nil {
|
||||||
log.ContextLogger(uploadCtx).WithError(err).Error("error comparing MD5 checksum")
|
log.ContextLogger(uploadCtx).WithError(err).Error("error comparing MD5 checksum")
|
||||||
return cr.n, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue