Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f8b2dfce12
commit
ea0085de54
87 changed files with 605 additions and 721 deletions
|
@ -44,7 +44,7 @@ docs-lint markdown:
|
||||||
- .default-retry
|
- .default-retry
|
||||||
- .docs:rules:docs-lint
|
- .docs:rules:docs-lint
|
||||||
# When updating the image version here, update it in /scripts/lint-doc.sh too.
|
# When updating the image version here, update it in /scripts/lint-doc.sh too.
|
||||||
image: "registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.12-vale-2.8.0-markdownlint-0.26.0"
|
image: registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.13-vale-2.10.2-markdownlint-0.26.0
|
||||||
stage: test
|
stage: test
|
||||||
needs: []
|
needs: []
|
||||||
script:
|
script:
|
||||||
|
@ -54,7 +54,7 @@ docs-lint links:
|
||||||
extends:
|
extends:
|
||||||
- .default-retry
|
- .default-retry
|
||||||
- .docs:rules:docs-lint
|
- .docs:rules:docs-lint
|
||||||
image: "registry.gitlab.com/gitlab-org/gitlab-docs/lint-html:alpine-3.12-ruby-2.7.2"
|
image: registry.gitlab.com/gitlab-org/gitlab-docs/lint-html:alpine-3.13-ruby-2.7.2
|
||||||
stage: test
|
stage: test
|
||||||
needs: []
|
needs: []
|
||||||
script:
|
script:
|
||||||
|
|
|
@ -1366,7 +1366,6 @@ RSpec/AnyInstanceOf:
|
||||||
- 'spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb'
|
- 'spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb'
|
||||||
- 'spec/views/layouts/_head.html.haml_spec.rb'
|
- 'spec/views/layouts/_head.html.haml_spec.rb'
|
||||||
- 'spec/views/projects/artifacts/_artifact.html.haml_spec.rb'
|
- 'spec/views/projects/artifacts/_artifact.html.haml_spec.rb'
|
||||||
- 'spec/views/shared/runners/show.html.haml_spec.rb'
|
|
||||||
- 'spec/workers/archive_trace_worker_spec.rb'
|
- 'spec/workers/archive_trace_worker_spec.rb'
|
||||||
- 'spec/workers/build_coverage_worker_spec.rb'
|
- 'spec/workers/build_coverage_worker_spec.rb'
|
||||||
- 'spec/workers/build_hooks_worker_spec.rb'
|
- 'spec/workers/build_hooks_worker_spec.rb'
|
||||||
|
|
|
@ -600,7 +600,6 @@ Rails/ShortI18n:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/models/project_services/chat_message/pipeline_message.rb'
|
- 'app/models/project_services/chat_message/pipeline_message.rb'
|
||||||
- 'app/uploaders/content_type_whitelist.rb'
|
- 'app/uploaders/content_type_whitelist.rb'
|
||||||
- 'spec/views/shared/runners/show.html.haml_spec.rb'
|
|
||||||
|
|
||||||
# Offense count: 1144
|
# Offense count: 1144
|
||||||
# Configuration parameters: ForbiddenMethods, AllowedMethods.
|
# Configuration parameters: ForbiddenMethods, AllowedMethods.
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 13.11.3 (2021-04-30)
|
||||||
|
|
||||||
|
### Fixed (1 change)
|
||||||
|
|
||||||
|
- Fix Instance-level Project Integration Management page for GitLab FOSS. !60354
|
||||||
|
|
||||||
|
|
||||||
## 13.11.2 (2021-04-27)
|
## 13.11.2 (2021-04-27)
|
||||||
|
|
||||||
### Security (5 changes)
|
### Security (5 changes)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
8128ec05cf75d8af4f0b4e422106cef4adf9b3a4
|
46f08adbf4930f6a9c56f37ef5e4106c5b50810f
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlSprintf, GlButton, GlAlert } from '@gitlab/ui';
|
import { GlSprintf, GlButton, GlAlert, GlCard } from '@gitlab/ui';
|
||||||
import Mousetrap from 'mousetrap';
|
import Mousetrap from 'mousetrap';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import Tracking from '~/tracking';
|
import Tracking from '~/tracking';
|
||||||
|
@ -34,7 +34,7 @@ export default {
|
||||||
recoveryCodeDownloadFilename: RECOVERY_CODE_DOWNLOAD_FILENAME,
|
recoveryCodeDownloadFilename: RECOVERY_CODE_DOWNLOAD_FILENAME,
|
||||||
i18n,
|
i18n,
|
||||||
mousetrap: null,
|
mousetrap: null,
|
||||||
components: { GlSprintf, GlButton, GlAlert, ClipboardButton },
|
components: { GlSprintf, GlButton, GlAlert, ClipboardButton, GlCard },
|
||||||
mixins: [Tracking.mixin()],
|
mixins: [Tracking.mixin()],
|
||||||
props: {
|
props: {
|
||||||
codes: {
|
codes: {
|
||||||
|
@ -116,8 +116,8 @@ export default {
|
||||||
</gl-sprintf>
|
</gl-sprintf>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div
|
<gl-card
|
||||||
class="codes-to-print gl-my-5 gl-p-5 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base"
|
class="codes-to-print gl-my-5"
|
||||||
data-testid="recovery-codes"
|
data-testid="recovery-codes"
|
||||||
data-qa-selector="codes_content"
|
data-qa-selector="codes_content"
|
||||||
>
|
>
|
||||||
|
@ -126,7 +126,7 @@ export default {
|
||||||
<span class="gl-font-monospace" data-qa-selector="code_content">{{ code }}</span>
|
<span class="gl-font-monospace" data-qa-selector="code_content">{{ code }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</gl-card>
|
||||||
<div class="gl-my-n2 gl-mx-n2 gl-display-flex gl-flex-wrap">
|
<div class="gl-my-n2 gl-mx-n2 gl-display-flex gl-flex-wrap">
|
||||||
<div class="gl-p-2">
|
<div class="gl-p-2">
|
||||||
<clipboard-button
|
<clipboard-button
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default {
|
||||||
const chartsToShow = ['pipelines'];
|
const chartsToShow = ['pipelines'];
|
||||||
|
|
||||||
if (this.shouldRenderDoraCharts) {
|
if (this.shouldRenderDoraCharts) {
|
||||||
chartsToShow.push('deployments', 'lead-time');
|
chartsToShow.push('deployment-frequency', 'lead-time');
|
||||||
}
|
}
|
||||||
|
|
||||||
return chartsToShow;
|
return chartsToShow;
|
||||||
|
@ -62,10 +62,10 @@ export default {
|
||||||
<pipeline-charts />
|
<pipeline-charts />
|
||||||
</gl-tab>
|
</gl-tab>
|
||||||
<template v-if="shouldRenderDoraCharts">
|
<template v-if="shouldRenderDoraCharts">
|
||||||
<gl-tab :title="__('Deployments')">
|
<gl-tab :title="__('Deployment frequency')">
|
||||||
<deployment-frequency-charts />
|
<deployment-frequency-charts />
|
||||||
</gl-tab>
|
</gl-tab>
|
||||||
<gl-tab :title="__('Lead Time')">
|
<gl-tab :title="__('Lead time')">
|
||||||
<lead-time-charts />
|
<lead-time-charts />
|
||||||
</gl-tab>
|
</gl-tab>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -74,6 +74,21 @@ export default {
|
||||||
// we need to show the "create from" input.
|
// we need to show the "create from" input.
|
||||||
this.showCreateFrom = true;
|
this.showCreateFrom = true;
|
||||||
},
|
},
|
||||||
|
shouldShowCreateTagOption(isLoading, matches, query) {
|
||||||
|
// Show the "create tag" option if:
|
||||||
|
return (
|
||||||
|
// we're not currently loading any results, and
|
||||||
|
!isLoading &&
|
||||||
|
// the search query isn't just whitespace, and
|
||||||
|
query.trim() &&
|
||||||
|
// the `matches` object is non-null, and
|
||||||
|
matches &&
|
||||||
|
// the tag name doesn't already exist
|
||||||
|
!matches.tags.list.some(
|
||||||
|
(tagInfo) => tagInfo.name.toUpperCase() === query.toUpperCase().trim(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
translations: {
|
translations: {
|
||||||
tagName: {
|
tagName: {
|
||||||
|
@ -111,7 +126,7 @@ export default {
|
||||||
>
|
>
|
||||||
<template #footer="{ isLoading, matches, query }">
|
<template #footer="{ isLoading, matches, query }">
|
||||||
<gl-dropdown-item
|
<gl-dropdown-item
|
||||||
v-if="!isLoading && matches && matches.tags.totalCount === 0"
|
v-if="shouldShowCreateTagOption(isLoading, matches, query)"
|
||||||
is-check-item
|
is-check-item
|
||||||
:is-checked="tagName === query"
|
:is-checked="tagName === query"
|
||||||
@click="createTagClicked(query)"
|
@click="createTagClicked(query)"
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { mapState, mapActions, mapGetters } from 'vuex';
|
||||||
import { s__, sprintf } from '~/locale';
|
import { s__, sprintf } from '~/locale';
|
||||||
import { componentNames } from '~/reports/components/issue_body';
|
import { componentNames } from '~/reports/components/issue_body';
|
||||||
import ReportSection from '~/reports/components/report_section.vue';
|
import ReportSection from '~/reports/components/report_section.vue';
|
||||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|
||||||
import createStore from './store';
|
import createStore from './store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -12,26 +11,12 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
ReportSection,
|
ReportSection,
|
||||||
},
|
},
|
||||||
mixins: [glFeatureFlagsMixin()],
|
|
||||||
props: {
|
props: {
|
||||||
headPath: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
headBlobPath: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
basePath: {
|
basePath: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
baseBlobPath: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
codequalityReportsPath: {
|
codequalityReportsPath: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -55,9 +40,6 @@ export default {
|
||||||
created() {
|
created() {
|
||||||
this.setPaths({
|
this.setPaths({
|
||||||
basePath: this.basePath,
|
basePath: this.basePath,
|
||||||
headPath: this.headPath,
|
|
||||||
baseBlobPath: this.baseBlobPath,
|
|
||||||
headBlobPath: this.headBlobPath,
|
|
||||||
reportsPath: this.codequalityReportsPath,
|
reportsPath: this.codequalityReportsPath,
|
||||||
helpPath: this.codequalityHelpPath,
|
helpPath: this.codequalityHelpPath,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,34 +1,23 @@
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import * as types from './mutation_types';
|
import * as types from './mutation_types';
|
||||||
import { parseCodeclimateMetrics, doCodeClimateComparison } from './utils/codequality_comparison';
|
import { parseCodeclimateMetrics } from './utils/codequality_parser';
|
||||||
|
|
||||||
export const setPaths = ({ commit }, paths) => commit(types.SET_PATHS, paths);
|
export const setPaths = ({ commit }, paths) => commit(types.SET_PATHS, paths);
|
||||||
|
|
||||||
export const fetchReports = ({ state, dispatch, commit }, diffFeatureFlagEnabled) => {
|
export const fetchReports = ({ state, dispatch, commit }) => {
|
||||||
commit(types.REQUEST_REPORTS);
|
commit(types.REQUEST_REPORTS);
|
||||||
|
|
||||||
if (diffFeatureFlagEnabled) {
|
|
||||||
return axios
|
|
||||||
.get(state.reportsPath)
|
|
||||||
.then(({ data }) => {
|
|
||||||
return dispatch('receiveReportsSuccess', {
|
|
||||||
newIssues: parseCodeclimateMetrics(data.new_errors, state.headBlobPath),
|
|
||||||
resolvedIssues: parseCodeclimateMetrics(data.resolved_errors, state.baseBlobPath),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => dispatch('receiveReportsError', error));
|
|
||||||
}
|
|
||||||
if (!state.basePath) {
|
if (!state.basePath) {
|
||||||
return dispatch('receiveReportsError');
|
return dispatch('receiveReportsError');
|
||||||
}
|
}
|
||||||
return Promise.all([axios.get(state.headPath), axios.get(state.basePath)])
|
return axios
|
||||||
.then((results) =>
|
.get(state.reportsPath)
|
||||||
doCodeClimateComparison(
|
.then(({ data }) => {
|
||||||
parseCodeclimateMetrics(results[0].data, state.headBlobPath),
|
return dispatch('receiveReportsSuccess', {
|
||||||
parseCodeclimateMetrics(results[1].data, state.baseBlobPath),
|
newIssues: parseCodeclimateMetrics(data.new_errors, state.headBlobPath),
|
||||||
),
|
resolvedIssues: parseCodeclimateMetrics(data.resolved_errors, state.baseBlobPath),
|
||||||
)
|
});
|
||||||
.then((data) => dispatch('receiveReportsSuccess', data))
|
})
|
||||||
.catch((error) => dispatch('receiveReportsError', error));
|
.catch((error) => dispatch('receiveReportsError', error));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,6 @@ import * as types from './mutation_types';
|
||||||
export default {
|
export default {
|
||||||
[types.SET_PATHS](state, paths) {
|
[types.SET_PATHS](state, paths) {
|
||||||
state.basePath = paths.basePath;
|
state.basePath = paths.basePath;
|
||||||
state.headPath = paths.headPath;
|
|
||||||
state.baseBlobPath = paths.baseBlobPath;
|
|
||||||
state.headBlobPath = paths.headBlobPath;
|
|
||||||
state.reportsPath = paths.reportsPath;
|
state.reportsPath = paths.reportsPath;
|
||||||
state.helpPath = paths.helpPath;
|
state.helpPath = paths.helpPath;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import CodeQualityComparisonWorker from '../../workers/codequality_comparison_worker';
|
|
||||||
|
|
||||||
export const parseCodeclimateMetrics = (issues = [], path = '') => {
|
export const parseCodeclimateMetrics = (issues = [], path = '') => {
|
||||||
return issues.map((issue) => {
|
return issues.map((issue) => {
|
||||||
const parsedIssue = {
|
const parsedIssue = {
|
||||||
|
@ -27,17 +25,3 @@ export const parseCodeclimateMetrics = (issues = [], path = '') => {
|
||||||
return parsedIssue;
|
return parsedIssue;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doCodeClimateComparison = (headIssues, baseIssues) => {
|
|
||||||
// Do these comparisons in worker threads to avoid blocking the main thread
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const worker = new CodeQualityComparisonWorker();
|
|
||||||
worker.addEventListener('message', ({ data }) =>
|
|
||||||
data.newIssues && data.resolvedIssues ? resolve(data) : reject(data),
|
|
||||||
);
|
|
||||||
worker.postMessage({
|
|
||||||
headIssues,
|
|
||||||
baseIssues,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { differenceBy } from 'lodash';
|
|
||||||
|
|
||||||
const KEY_TO_FILTER_BY = 'fingerprint';
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
self.addEventListener('message', (e) => {
|
|
||||||
const { data } = e;
|
|
||||||
|
|
||||||
if (data === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { headIssues, baseIssues } = data;
|
|
||||||
|
|
||||||
if (!headIssues || !baseIssues) {
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
return self.postMessage({});
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
self.postMessage({
|
|
||||||
newIssues: differenceBy(headIssues, baseIssues, KEY_TO_FILTER_BY),
|
|
||||||
resolvedIssues: differenceBy(baseIssues, headIssues, KEY_TO_FILTER_BY),
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
return self.close();
|
|
||||||
});
|
|
|
@ -460,9 +460,6 @@ export default {
|
||||||
<grouped-codequality-reports-app
|
<grouped-codequality-reports-app
|
||||||
v-if="shouldRenderCodeQuality"
|
v-if="shouldRenderCodeQuality"
|
||||||
:base-path="mr.codeclimate.base_path"
|
:base-path="mr.codeclimate.base_path"
|
||||||
:head-path="mr.codeclimate.head_path"
|
|
||||||
:head-blob-path="mr.headBlobPath"
|
|
||||||
:base-blob-path="mr.baseBlobPath"
|
|
||||||
:codequality-reports-path="mr.codequalityReportsPath"
|
:codequality-reports-path="mr.codequalityReportsPath"
|
||||||
:codequality-help-path="mr.codequalityHelpPath"
|
:codequality-help-path="mr.codequalityHelpPath"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex';
|
import { mapGetters, mapState } from 'vuex';
|
||||||
|
|
||||||
import DropdownContentsCreateView from './dropdown_contents_create_view.vue';
|
import DropdownContentsCreateView from './dropdown_contents_create_view.vue';
|
||||||
import DropdownContentsLabelsView from './dropdown_contents_labels_view.vue';
|
import DropdownContentsLabelsView from './dropdown_contents_labels_view.vue';
|
||||||
|
@ -18,6 +18,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['showDropdownContentsCreateView']),
|
...mapState(['showDropdownContentsCreateView']),
|
||||||
|
...mapGetters(['isDropdownVariantSidebar']),
|
||||||
dropdownContentsView() {
|
dropdownContentsView() {
|
||||||
if (this.showDropdownContentsCreateView) {
|
if (this.showDropdownContentsCreateView) {
|
||||||
return 'dropdown-contents-create-view';
|
return 'dropdown-contents-create-view';
|
||||||
|
@ -25,11 +26,8 @@ export default {
|
||||||
return 'dropdown-contents-labels-view';
|
return 'dropdown-contents-labels-view';
|
||||||
},
|
},
|
||||||
directionStyle() {
|
directionStyle() {
|
||||||
if (this.renderOnTop) {
|
const bottom = this.isDropdownVariantSidebar ? '3rem' : '2rem';
|
||||||
return { bottom: '100%' };
|
return this.renderOnTop ? { bottom } : {};
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -37,7 +35,7 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="labels-select-dropdown-contents w-100 mt-1 mb-3 py-2 rounded-top rounded-bottom position-absolute"
|
class="labels-select-dropdown-contents gl-w-full gl-my-2 gl-py-3 gl-rounded-base gl-absolute"
|
||||||
data-qa-selector="labels_dropdown_content"
|
data-qa-selector="labels_dropdown_content"
|
||||||
:style="directionStyle"
|
:style="directionStyle"
|
||||||
>
|
>
|
||||||
|
|
|
@ -83,12 +83,13 @@ export default {
|
||||||
const highlightedLabel = this.$refs.labelsListContainer.querySelector('.is-focused');
|
const highlightedLabel = this.$refs.labelsListContainer.querySelector('.is-focused');
|
||||||
|
|
||||||
if (highlightedLabel) {
|
if (highlightedLabel) {
|
||||||
const rect = highlightedLabel.getBoundingClientRect();
|
const container = this.$refs.labelsListContainer.getBoundingClientRect();
|
||||||
if (rect.bottom > this.$refs.labelsListContainer.clientHeight) {
|
const label = highlightedLabel.getBoundingClientRect();
|
||||||
highlightedLabel.scrollIntoView(false);
|
|
||||||
}
|
if (label.bottom > container.bottom) {
|
||||||
if (rect.top < 0) {
|
this.$refs.labelsListContainer.scrollTop += label.bottom - container.bottom;
|
||||||
highlightedLabel.scrollIntoView();
|
} else if (label.top < container.top) {
|
||||||
|
this.$refs.labelsListContainer.scrollTop -= container.top - label.top;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default {
|
||||||
const { label, highlight, isLabelSet } = props;
|
const { label, highlight, isLabelSet } = props;
|
||||||
|
|
||||||
const labelColorBox = h('span', {
|
const labelColorBox = h('span', {
|
||||||
class: 'dropdown-label-box',
|
class: 'dropdown-label-box gl-flex-shrink-0 gl-top-0 gl-mr-3',
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: label.color,
|
backgroundColor: label.color,
|
||||||
},
|
},
|
||||||
|
@ -33,7 +33,7 @@ export default {
|
||||||
|
|
||||||
const checkedIcon = h(GlIcon, {
|
const checkedIcon = h(GlIcon, {
|
||||||
class: {
|
class: {
|
||||||
'mr-2 align-self-center': true,
|
'gl-mr-3 gl-flex-shrink-0': true,
|
||||||
hidden: !isLabelSet,
|
hidden: !isLabelSet,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -43,7 +43,7 @@ export default {
|
||||||
|
|
||||||
const noIcon = h('span', {
|
const noIcon = h('span', {
|
||||||
class: {
|
class: {
|
||||||
'mr-3 pr-2': true,
|
'gl-mr-5 gl-pr-3': true,
|
||||||
hidden: isLabelSet,
|
hidden: isLabelSet,
|
||||||
},
|
},
|
||||||
attrs: {
|
attrs: {
|
||||||
|
@ -56,7 +56,7 @@ export default {
|
||||||
const labelLink = h(
|
const labelLink = h(
|
||||||
GlLink,
|
GlLink,
|
||||||
{
|
{
|
||||||
class: 'd-flex align-items-baseline text-break-word label-item',
|
class: 'gl-display-flex gl-align-items-center label-item gl-text-black-normal',
|
||||||
on: {
|
on: {
|
||||||
click: () => {
|
click: () => {
|
||||||
listeners.clickLabel(label);
|
listeners.clickLabel(label);
|
||||||
|
@ -70,8 +70,8 @@ export default {
|
||||||
'li',
|
'li',
|
||||||
{
|
{
|
||||||
class: {
|
class: {
|
||||||
'd-block': true,
|
'gl-display-block': true,
|
||||||
'text-left': true,
|
'gl-text-left': true,
|
||||||
'is-focused': highlight,
|
'is-focused': highlight,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -268,7 +268,7 @@ export default {
|
||||||
this.$emit('toggleCollapse');
|
this.$emit('toggleCollapse');
|
||||||
},
|
},
|
||||||
setContentIsOnViewport(showDropdownContents) {
|
setContentIsOnViewport(showDropdownContents) {
|
||||||
if (!this.isDropdownVariantEmbedded || !showDropdownContents) {
|
if (!showDropdownContents) {
|
||||||
this.contentIsOnViewport = true;
|
this.contentIsOnViewport = true;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -276,8 +276,7 @@ export default {
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.dropdownContents) {
|
if (this.$refs.dropdownContents) {
|
||||||
const offset = { top: 100 };
|
this.contentIsOnViewport = isInViewport(this.$refs.dropdownContents.$el);
|
||||||
this.contentIsOnViewport = isInViewport(this.$refs.dropdownContents.$el, offset);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -313,6 +312,7 @@ export default {
|
||||||
<dropdown-contents
|
<dropdown-contents
|
||||||
v-show="dropdownButtonVisible && showDropdownContents"
|
v-show="dropdownButtonVisible && showDropdownContents"
|
||||||
ref="dropdownContents"
|
ref="dropdownContents"
|
||||||
|
:render-on-top="!contentIsOnViewport"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="isDropdownVariantStandalone || isDropdownVariantEmbedded">
|
<template v-if="isDropdownVariantStandalone || isDropdownVariantEmbedded">
|
||||||
|
|
|
@ -37,10 +37,6 @@
|
||||||
|
|
||||||
.file-title {
|
.file-title {
|
||||||
@include gl-font-monospace;
|
@include gl-font-monospace;
|
||||||
line-height: 35px;
|
|
||||||
padding-top: 7px;
|
|
||||||
padding-bottom: 7px;
|
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-ref {
|
.editor-ref {
|
||||||
|
@ -69,19 +65,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-buttons {
|
.file-buttons {
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.soft-wrap-toggle {
|
.soft-wrap-toggle {
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
font-family: $regular-font;
|
font-family: $regular-font;
|
||||||
margin: 0 $btn-side-margin;
|
margin-left: $gl-padding-8;
|
||||||
|
|
||||||
.soft-wrap {
|
.soft-wrap {
|
||||||
display: block;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-wrap {
|
.no-wrap {
|
||||||
|
@ -94,7 +86,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-wrap {
|
.no-wrap {
|
||||||
display: block;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,17 +103,21 @@
|
||||||
.new-file-path {
|
.new-file-path {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 3px;
|
margin-top: $gl-padding-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-buttons {
|
.file-buttons {
|
||||||
display: block;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 10px;
|
|
||||||
|
.md-header-toolbar {
|
||||||
|
margin: $gl-padding 0;
|
||||||
|
}
|
||||||
|
|
||||||
.soft-wrap-toggle {
|
.soft-wrap-toggle {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 3px 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: map-get($grid-breakpoints, md)-1) {
|
@media(max-width: map-get($grid-breakpoints, md)-1) {
|
||||||
|
|
|
@ -35,6 +35,6 @@ module RequiresWhitelistedMonitoringClient
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_404
|
def render_404
|
||||||
render file: Rails.root.join('public', '404'), layout: false, status: '404'
|
render "errors/not_found", layout: "errors", status: :not_found
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,6 @@ class Groups::RunnersController < Groups::ApplicationController
|
||||||
feature_category :continuous_integration
|
feature_category :continuous_integration
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render 'shared/runners/show'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
|
|
|
@ -32,6 +32,7 @@ class Projects::HooksController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
|
redirect_to(action: :index) unless hook
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|
|
@ -108,10 +108,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
params[:issue] ||= ActionController::Parameters.new(
|
params[:issue] ||= ActionController::Parameters.new(
|
||||||
assignee_ids: ""
|
assignee_ids: ""
|
||||||
)
|
)
|
||||||
build_params = issue_create_params.merge(
|
build_params = issue_params.merge(
|
||||||
merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
|
merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
|
||||||
discussion_to_resolve: params[:discussion_to_resolve],
|
discussion_to_resolve: params[:discussion_to_resolve],
|
||||||
confidential: !!Gitlab::Utils.to_boolean(issue_create_params[:confidential])
|
confidential: !!Gitlab::Utils.to_boolean(issue_params[:confidential])
|
||||||
)
|
)
|
||||||
service = ::Issues::BuildService.new(project, current_user, build_params)
|
service = ::Issues::BuildService.new(project, current_user, build_params)
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
create_params = issue_create_params.merge(spammable_params).merge(
|
create_params = issue_params.merge(spammable_params).merge(
|
||||||
merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
|
merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
|
||||||
discussion_to_resolve: params[:discussion_to_resolve]
|
discussion_to_resolve: params[:discussion_to_resolve]
|
||||||
)
|
)
|
||||||
|
@ -314,17 +314,8 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
task_num
|
task_num
|
||||||
lock_version
|
lock_version
|
||||||
discussion_locked
|
discussion_locked
|
||||||
] + [{ label_ids: [], assignee_ids: [], update_task: [:index, :checked, :line_number, :line_source] }]
|
|
||||||
end
|
|
||||||
|
|
||||||
def issue_create_params
|
|
||||||
create_params = %i[
|
|
||||||
issue_type
|
issue_type
|
||||||
]
|
] + [{ label_ids: [], assignee_ids: [], update_task: [:index, :checked, :line_number, :line_source] }]
|
||||||
|
|
||||||
params.require(:issue).permit(
|
|
||||||
*create_params
|
|
||||||
).merge(issue_params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reorder_params
|
def reorder_params
|
||||||
|
|
|
@ -48,7 +48,6 @@ class Projects::RunnersController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render 'shared/runners/show'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def toggle_shared_runners
|
def toggle_shared_runners
|
||||||
|
|
|
@ -26,6 +26,8 @@ module GroupsHelper
|
||||||
applications#show
|
applications#show
|
||||||
applications#edit
|
applications#edit
|
||||||
packages_and_registries#index
|
packages_and_registries#index
|
||||||
|
groups/runners#show
|
||||||
|
groups/runners#edit
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -723,6 +723,8 @@ module ProjectsHelper
|
||||||
badges#index
|
badges#index
|
||||||
pages#show
|
pages#show
|
||||||
packages_and_registries#index
|
packages_and_registries#index
|
||||||
|
projects/runners#show
|
||||||
|
projects/runners#edit
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebexTeamsService < ChatNotificationService
|
class WebexTeamsService < ChatNotificationService
|
||||||
|
include ActionView::Helpers::UrlHelper
|
||||||
|
|
||||||
def title
|
def title
|
||||||
'Webex Teams'
|
s_("WebexTeamsService|Webex Teams")
|
||||||
end
|
end
|
||||||
|
|
||||||
def description
|
def description
|
||||||
'Receive event notifications in Webex Teams'
|
s_("WebexTeamsService|Send notifications about project events to Webex Teams.")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.to_param
|
def self.to_param
|
||||||
|
@ -14,13 +16,8 @@ class WebexTeamsService < ChatNotificationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def help
|
def help
|
||||||
'This service sends notifications about projects events to a Webex Teams conversation.<br />
|
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/webex_teams'), target: '_blank', rel: 'noopener noreferrer'
|
||||||
To set up this service:
|
s_("WebexTeamsService|Send notifications about project events to a Webex Teams conversation. %{docs_link}") % { docs_link: docs_link.html_safe }
|
||||||
<ol>
|
|
||||||
<li><a href="https://apphub.webex.com/teams/applications/incoming-webhooks-cisco-systems">Set up an incoming webhook for your conversation</a>. All notifications will come to this conversation.</li>
|
|
||||||
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
|
|
||||||
<li>Select events below to enable notifications.</li>
|
|
||||||
</ol>'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def event_field(event)
|
def event_field(event)
|
||||||
|
@ -36,7 +33,7 @@ class WebexTeamsService < ChatNotificationService
|
||||||
|
|
||||||
def default_fields
|
def default_fields
|
||||||
[
|
[
|
||||||
{ type: 'text', name: 'webhook', placeholder: "e.g. https://api.ciscospark.com/v1/webhooks/incoming/…", required: true },
|
{ type: 'text', name: 'webhook', placeholder: "https://api.ciscospark.com/v1/webhooks/incoming/...", required: true },
|
||||||
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
|
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
|
||||||
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
|
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
|
||||||
]
|
]
|
||||||
|
|
|
@ -24,10 +24,11 @@ module Issues
|
||||||
def filter_params(issue)
|
def filter_params(issue)
|
||||||
super
|
super
|
||||||
|
|
||||||
# filter confidential in `Issues::UpdateService` and not in `IssuableBaseService#filtr_params`
|
# filter confidential in `Issues::UpdateService` and not in `IssuableBaseService#filter_params`
|
||||||
# because we do allow users that cannot admin issues to set confidential flag when creating an issue
|
# because we do allow users that cannot admin issues to set confidential flag when creating an issue
|
||||||
unless can_admin_issuable?(issue)
|
unless can_admin_issuable?(issue)
|
||||||
params.delete(:confidential)
|
params.delete(:confidential)
|
||||||
|
params.delete(:issue_type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
trigger: "focus",
|
trigger: "focus",
|
||||||
content: s_("AdminArea|All users created in the instance, including users who are not %{billable_users_link_start}billable users%{billable_users_link_end}.").html_safe % { billable_users_link_start: billable_users_link_start, billable_users_link_end: '</a>'.html_safe },
|
content: s_("AdminArea|All users created in the instance, including users who are not %{billable_users_link_start}billable users%{billable_users_link_end}.").html_safe % { billable_users_link_start: billable_users_link_start, billable_users_link_end: '</a>'.html_safe },
|
||||||
} }
|
} }
|
||||||
= sprite_icon('question', size: 16, css_class: 'gl-text-gray-700')
|
= sprite_icon('question-o', size: 16, css_class: 'gl-text-blue-600')
|
||||||
.gl-mt-3.text-uppercase
|
.gl-mt-3.text-uppercase
|
||||||
= s_('AdminArea|Users')
|
= s_('AdminArea|Users')
|
||||||
= link_to(s_('AdminArea|Users statistics'), admin_dashboard_stats_path, class: "text-capitalize gl-ml-2")
|
= link_to(s_('AdminArea|Users statistics'), admin_dashboard_stats_path, class: "text-capitalize gl-ml-2")
|
||||||
|
|
|
@ -23,8 +23,8 @@
|
||||||
%code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token)
|
%code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token)
|
||||||
= render_if_exists 'admin/health_check/health_check_url'
|
= render_if_exists 'admin/health_check/health_check_url'
|
||||||
%hr
|
%hr
|
||||||
.card
|
.gl-card
|
||||||
.card-header
|
.gl-card-header
|
||||||
Current Status:
|
Current Status:
|
||||||
- if no_errors
|
- if no_errors
|
||||||
= sprite_icon('check', css_class: 'cgreen')
|
= sprite_icon('check', css_class: 'cgreen')
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
- else
|
- else
|
||||||
= sprite_icon('warning-solid', css_class: 'cred')
|
= sprite_icon('warning-solid', css_class: 'cred')
|
||||||
#{ s_('HealthCheck|Unhealthy') }
|
#{ s_('HealthCheck|Unhealthy') }
|
||||||
.card-body
|
.gl-card-body
|
||||||
- if no_errors
|
- if no_errors
|
||||||
#{ s_('HealthCheck|No Health Problems Detected') }
|
#{ s_('HealthCheck|No Health Problems Detected') }
|
||||||
- else
|
- else
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= _('Recent Deliveries')
|
= _('Recent Deliveries')
|
||||||
%p= _('When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong.')
|
%p= _('When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong.')
|
||||||
.col-lg-9
|
.col-lg-9
|
||||||
- if hook_logs.any?
|
- if hook_logs.present?
|
||||||
%table.table
|
%table.table
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
- add_page_specific_style 'page_bundles/ci_status'
|
- add_page_specific_style 'page_bundles/ci_status'
|
||||||
|
|
||||||
- page_title @runner.short_sha
|
- breadcrumb_title @runner.short_sha
|
||||||
|
- page_title "##{@runner.id} (#{@runner.short_sha})"
|
||||||
- add_to_breadcrumbs _('Runners'), admin_runners_path
|
- add_to_breadcrumbs _('Runners'), admin_runners_path
|
||||||
- breadcrumb_title page_title
|
|
||||||
|
|
||||||
- if Feature.enabled?(:runner_detailed_view_vue_ui, current_user, default_enabled: :yaml)
|
- if Feature.enabled?(:runner_detailed_view_vue_ui, current_user, default_enabled: :yaml)
|
||||||
#js-runner-detail{ data: {runner_id: @runner.id} }
|
#js-runner-detail{ data: {runner_id: @runner.id} }
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
- page_title _('Edit'), "#{@runner.description} ##{@runner.id}", _('Runners')
|
- breadcrumb_title _('Edit')
|
||||||
|
- page_title _('Edit'), "##{@runner.id} (#{@runner.short_sha})"
|
||||||
|
- add_to_breadcrumbs _('CI/CD Settings'), group_settings_ci_cd_path(@group)
|
||||||
|
- add_to_breadcrumbs "#{@runner.short_sha}", group_runner_path(@group, @runner)
|
||||||
|
|
||||||
%h2.page-title
|
%h2.page-title
|
||||||
= s_('Runners|Runner #%{runner_id}' % { runner_id: @runner.id })
|
= s_('Runners|Runner #%{runner_id}' % { runner_id: @runner.id })
|
||||||
|
|
3
app/views/groups/runners/show.html.haml
Normal file
3
app/views/groups/runners/show.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
- add_to_breadcrumbs _('CI/CD Settings'), group_settings_ci_cd_path(@group)
|
||||||
|
|
||||||
|
= render 'shared/runners/runner_details', runner: @runner
|
|
@ -92,7 +92,7 @@
|
||||||
= link_to help_path, class: 'header-help-dropdown-toggle', data: { toggle: "dropdown" } do
|
= link_to help_path, class: 'header-help-dropdown-toggle', data: { toggle: "dropdown" } do
|
||||||
%span.gl-sr-only
|
%span.gl-sr-only
|
||||||
= s_('Nav|Help')
|
= s_('Nav|Help')
|
||||||
= sprite_icon('question')
|
= sprite_icon('question-o')
|
||||||
%span.notification-dot.rounded-circle.gl-absolute
|
%span.notification-dot.rounded-circle.gl-absolute
|
||||||
= sprite_icon('chevron-down', css_class: 'caret-down')
|
= sprite_icon('chevron-down', css_class: 'caret-down')
|
||||||
.dropdown-menu.dropdown-menu-right
|
.dropdown-menu.dropdown-menu-right
|
||||||
|
|
|
@ -170,7 +170,7 @@
|
||||||
%span
|
%span
|
||||||
= _('Repository')
|
= _('Repository')
|
||||||
|
|
||||||
= nav_link(controller: :ci_cd) do
|
= nav_link(controller: [:ci_cd, 'groups/runners']) do
|
||||||
= link_to group_settings_ci_cd_path(@group), title: _('CI/CD') do
|
= link_to group_settings_ci_cd_path(@group), title: _('CI/CD') do
|
||||||
%span
|
%span
|
||||||
= _('CI/CD')
|
= _('CI/CD')
|
||||||
|
|
|
@ -193,7 +193,7 @@
|
||||||
%span
|
%span
|
||||||
= _('Repository')
|
= _('Repository')
|
||||||
- if !@project.archived? && @project.feature_available?(:builds, current_user)
|
- if !@project.archived? && @project.feature_available?(:builds, current_user)
|
||||||
= nav_link(controller: :ci_cd) do
|
= nav_link(controller: [:ci_cd, 'projects/runners']) do
|
||||||
= link_to project_settings_ci_cd_path(@project), title: _('CI/CD') do
|
= link_to project_settings_ci_cd_path(@project), title: _('CI/CD') do
|
||||||
%span
|
%span
|
||||||
= _('CI/CD')
|
= _('CI/CD')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.form-actions
|
.form-actions.gl-display-flex
|
||||||
= button_tag 'Commit changes', id: 'commit-changes', class: 'gl-button btn btn-confirm js-commit-button qa-commit-button'
|
= button_tag 'Commit changes', id: 'commit-changes', class: 'gl-button btn btn-confirm js-commit-button qa-commit-button'
|
||||||
|
|
||||||
= link_to 'Cancel', cancel_path,
|
= link_to 'Cancel', cancel_path,
|
||||||
class: 'gl-button btn btn-default btn-cancel', data: {confirm: leave_edit_message}
|
class: 'gl-button btn btn-default gl-ml-3', data: {confirm: leave_edit_message}
|
||||||
|
|
||||||
= render 'shared/projects/edit_information'
|
= render 'shared/projects/edit_information'
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name)
|
- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name)
|
||||||
|
|
||||||
.file-holder-bottom-radius.file-holder.file.gl-mb-3
|
.file-holder-bottom-radius.file-holder.file.gl-mb-3
|
||||||
.js-file-title.file-title.align-items-center.clearfix{ data: { current_action: action } }
|
.js-file-title.file-title.gl-display-flex.gl-align-items-center.clearfix{ data: { current_action: action } }
|
||||||
.editor-ref.block-truncated.has-tooltip{ title: ref }
|
.editor-ref.block-truncated.has-tooltip{ title: ref }
|
||||||
= sprite_icon('fork', size: 12)
|
= sprite_icon('fork', size: 12)
|
||||||
= ref
|
= ref
|
||||||
|
@ -26,16 +26,18 @@
|
||||||
dismiss_key: @project.id,
|
dismiss_key: @project.id,
|
||||||
human_access: human_access } }
|
human_access: human_access } }
|
||||||
|
|
||||||
.file-buttons
|
.file-buttons.gl-display-flex.gl-align-items-center.gl-justify-content-end
|
||||||
- if is_markdown
|
- if is_markdown
|
||||||
= render 'shared/blob/markdown_buttons', show_fullscreen_button: false
|
= render 'shared/blob/markdown_buttons', show_fullscreen_button: false
|
||||||
= button_tag class: 'soft-wrap-toggle btn gl-button', type: 'button', tabindex: '-1' do
|
= button_tag class: 'soft-wrap-toggle btn gl-button btn-default', type: 'button', tabindex: '-1' do
|
||||||
%span.no-wrap
|
.no-wrap
|
||||||
= custom_icon('icon_no_wrap')
|
= sprite_icon('soft-unwrap', css_class: 'gl-button-icon')
|
||||||
No wrap
|
%span.gl-button-text
|
||||||
%span.soft-wrap
|
No wrap
|
||||||
= custom_icon('icon_soft_wrap')
|
.soft-wrap
|
||||||
Soft wrap
|
= sprite_icon('soft-wrap', css_class: 'gl-button-icon')
|
||||||
|
%span.gl-button-text
|
||||||
|
Soft wrap
|
||||||
|
|
||||||
.file-editor.code
|
.file-editor.code
|
||||||
.js-edit-mode-pane.qa-editor#editor{ data: { 'editor-loading': true } }<
|
.js-edit-mode-pane.qa-editor#editor{ data: { 'editor-loading': true } }<
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
Recent Deliveries
|
Recent Deliveries
|
||||||
%p When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong.
|
%p When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong.
|
||||||
.col-lg-9
|
.col-lg-9
|
||||||
- if hook_logs.any?
|
- if hook_logs.present?
|
||||||
%table.table
|
%table.table
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
|
|
|
@ -51,11 +51,12 @@
|
||||||
= render 'new_project_fields', f: f, project_name_id: "blank-project-name"
|
= render 'new_project_fields', f: f, project_name_id: "blank-project-name"
|
||||||
|
|
||||||
#create-from-template-pane.tab-pane.js-toggle-container.px-0.pb-0{ class: active_when(active_tab == 'template'), role: 'tabpanel' }
|
#create-from-template-pane.tab-pane.js-toggle-container.px-0.pb-0{ class: active_when(active_tab == 'template'), role: 'tabpanel' }
|
||||||
.card.card-slim.m-4.p-4
|
.gl-card.gl-my-5
|
||||||
%div
|
.gl-card-body
|
||||||
- contributing_templates_url = 'https://gitlab.com/gitlab-org/project-templates/contributing'
|
%div
|
||||||
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: contributing_templates_url }
|
- contributing_templates_url = 'https://gitlab.com/gitlab-org/project-templates/contributing'
|
||||||
= _('Learn how to %{link_start}contribute to the built-in templates%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: contributing_templates_url }
|
||||||
|
= _('Learn how to %{link_start}contribute to the built-in templates%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||||
= form_for @project, html: { class: 'new_project' } do |f|
|
= form_for @project, html: { class: 'new_project' } do |f|
|
||||||
.project-template
|
.project-template
|
||||||
.form-group
|
.form-group
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
- page_title _('Edit'), "#{@runner.description} ##{@runner.id}", _('Runners')
|
- breadcrumb_title _('Edit')
|
||||||
|
- page_title _('Edit'), "##{@runner.id} (#{@runner.short_sha})"
|
||||||
|
- add_to_breadcrumbs _('CI/CD Settings'), project_settings_ci_cd_path(@project)
|
||||||
|
- add_to_breadcrumbs "#{@runner.short_sha}", project_runner_path(@project, @runner)
|
||||||
|
|
||||||
%h2.page-title
|
%h2.page-title
|
||||||
= s_('Runners|Runner #%{runner_id}' % { runner_id: @runner.id })
|
= s_('Runners|Runner #%{runner_id}' % { runner_id: @runner.id })
|
||||||
|
|
3
app/views/projects/runners/show.html.haml
Normal file
3
app/views/projects/runners/show.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
- add_to_breadcrumbs _('CI/CD Settings'), project_settings_ci_cd_path(@project)
|
||||||
|
|
||||||
|
= render 'shared/runners/runner_details', runner: @runner
|
|
@ -1,8 +1,9 @@
|
||||||
- page_title "#{@runner.description} ##{@runner.id}", _("Runners")
|
- breadcrumb_title runner.short_sha
|
||||||
|
- page_title "##{runner.id} (#{runner.short_sha})"
|
||||||
|
|
||||||
%h2.page-title
|
%h2.page-title
|
||||||
= s_('Runners|Runner #%{runner_id}' % { runner_id: @runner.id })
|
= s_('Runners|Runner #%{runner_id}' % { runner_id: runner.id })
|
||||||
= render 'shared/runners/runner_type_badge', runner: @runner
|
= render 'shared/runners/runner_type_badge', runner: runner
|
||||||
|
|
||||||
.table-holder
|
.table-holder
|
||||||
%table.table
|
%table.table
|
||||||
|
@ -12,51 +13,51 @@
|
||||||
%th= s_('Runners|Value')
|
%th= s_('Runners|Value')
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Active')
|
%td= s_('Runners|Active')
|
||||||
%td= @runner.active? ? _('Yes') : _('No')
|
%td= runner.active? ? _('Yes') : _('No')
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Protected')
|
%td= s_('Runners|Protected')
|
||||||
%td= @runner.ref_protected? ? _('Yes') : _('No')
|
%td= runner.ref_protected? ? _('Yes') : _('No')
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Can run untagged jobs')
|
%td= s_('Runners|Can run untagged jobs')
|
||||||
%td= @runner.run_untagged? ? _('Yes') : _('No')
|
%td= runner.run_untagged? ? _('Yes') : _('No')
|
||||||
- unless @runner.group_type?
|
- unless runner.group_type?
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Locked to this project')
|
%td= s_('Runners|Locked to this project')
|
||||||
%td= @runner.locked? ? _('Yes') : _('No')
|
%td= runner.locked? ? _('Yes') : _('No')
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Tags')
|
%td= s_('Runners|Tags')
|
||||||
%td
|
%td
|
||||||
- @runner.tag_list.sort.each do |tag|
|
- runner.tag_list.sort.each do |tag|
|
||||||
%span.badge.badge-primary
|
%span.badge.badge-primary
|
||||||
= tag
|
= tag
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Name')
|
%td= s_('Runners|Name')
|
||||||
%td= @runner.name
|
%td= runner.name
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Version')
|
%td= s_('Runners|Version')
|
||||||
%td= @runner.version
|
%td= runner.version
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|IP Address')
|
%td= s_('Runners|IP Address')
|
||||||
%td= @runner.ip_address
|
%td= runner.ip_address
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Revision')
|
%td= s_('Runners|Revision')
|
||||||
%td= @runner.revision
|
%td= runner.revision
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Platform')
|
%td= s_('Runners|Platform')
|
||||||
%td= @runner.platform
|
%td= runner.platform
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Architecture')
|
%td= s_('Runners|Architecture')
|
||||||
%td= @runner.architecture
|
%td= runner.architecture
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Description')
|
%td= s_('Runners|Description')
|
||||||
%td= @runner.description
|
%td= runner.description
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Maximum job timeout')
|
%td= s_('Runners|Maximum job timeout')
|
||||||
%td= @runner.maximum_timeout_human_readable
|
%td= runner.maximum_timeout_human_readable
|
||||||
%tr
|
%tr
|
||||||
%td= s_('Runners|Last contact')
|
%td= s_('Runners|Last contact')
|
||||||
%td
|
%td
|
||||||
- if @runner.contacted_at
|
- if runner.contacted_at
|
||||||
= time_ago_with_tooltip @runner.contacted_at
|
= time_ago_with_tooltip runner.contacted_at
|
||||||
- else
|
- else
|
||||||
= s_('Never')
|
= s_('Never')
|
|
@ -5,7 +5,7 @@
|
||||||
%li{ class: active_when(params[:scope].nil?) }
|
%li{ class: active_when(params[:scope].nil?) }
|
||||||
= link_to subject_snippets_path(subject) do
|
= link_to subject_snippets_path(subject) do
|
||||||
= _("All")
|
= _("All")
|
||||||
%span.badge.badge-pill
|
%span.badge.badge-muted.badge-pill.gl-badge.sm
|
||||||
- if include_private
|
- if include_private
|
||||||
= counts[:total]
|
= counts[:total]
|
||||||
- else
|
- else
|
||||||
|
@ -15,17 +15,17 @@
|
||||||
%li{ class: active_when(params[:scope] == "are_private") }
|
%li{ class: active_when(params[:scope] == "are_private") }
|
||||||
= link_to subject_snippets_path(subject, scope: 'are_private') do
|
= link_to subject_snippets_path(subject, scope: 'are_private') do
|
||||||
= _("Private")
|
= _("Private")
|
||||||
%span.badge.badge-pill
|
%span.badge.badge-muted.badge-pill.gl-badge.sm
|
||||||
= counts[:are_private]
|
= counts[:are_private]
|
||||||
|
|
||||||
%li{ class: active_when(params[:scope] == "are_internal") }
|
%li{ class: active_when(params[:scope] == "are_internal") }
|
||||||
= link_to subject_snippets_path(subject, scope: 'are_internal') do
|
= link_to subject_snippets_path(subject, scope: 'are_internal') do
|
||||||
= _("Internal")
|
= _("Internal")
|
||||||
%span.badge.badge-pill
|
%span.badge.badge-muted.badge-pill.gl-badge.sm
|
||||||
= counts[:are_internal]
|
= counts[:are_internal]
|
||||||
|
|
||||||
%li{ class: active_when(params[:scope] == "are_public") }
|
%li{ class: active_when(params[:scope] == "are_public") }
|
||||||
= link_to subject_snippets_path(subject, scope: 'are_public') do
|
= link_to subject_snippets_path(subject, scope: 'are_public') do
|
||||||
= _("Public")
|
= _("Public")
|
||||||
%span.badge.badge-pill
|
%span.badge.badge-muted.badge-pill.gl-badge.sm
|
||||||
= counts[:are_public]
|
= counts[:are_public]
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Update edit file buttons and spacing
|
||||||
|
merge_request: 60318
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Ensure we never error in web hook logs
|
||||||
|
merge_request: 60408
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix breadcrumbs and navigation in runner details pages
|
||||||
|
merge_request: 60129
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Refactor Webex Teams integration settings text
|
||||||
|
merge_request: 60565
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
title: Fix Instance-level Project Integration Management page for GitLab FOSS
|
|
||||||
merge_request: 60354
|
|
||||||
author:
|
|
||||||
type: fixed
|
|
5
changelogs/unreleased/gl-badge-snippets.yml
Normal file
5
changelogs/unreleased/gl-badge-snippets.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add gl-badge for badges in snippets nav
|
||||||
|
merge_request: 57966
|
||||||
|
author: Yogi (@yo)
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix tag matching behavior on New Release page
|
||||||
|
merge_request: 60035
|
||||||
|
author:
|
||||||
|
type: fixed
|
5
changelogs/unreleased/sy-change-issue-type.yml
Normal file
5
changelogs/unreleased/sy-change-issue-type.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add internal API support for updating issue types on issues
|
||||||
|
merge_request: 60173
|
||||||
|
author:
|
||||||
|
type: added
|
5
changelogs/unreleased/yogi-gl-card-billing.yml
Normal file
5
changelogs/unreleased/yogi-gl-card-billing.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Move card in billing page to gl-card utility class
|
||||||
|
merge_request: 59138
|
||||||
|
author: Yogi (@yo)
|
||||||
|
type: changed
|
5
changelogs/unreleased/yogi-gl-card-recovery-codes.yml
Normal file
5
changelogs/unreleased/yogi-gl-card-recovery-codes.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Move 2fa recovery codes to GlCard component
|
||||||
|
merge_request: 59219
|
||||||
|
author: Yogi (@yo)
|
||||||
|
type: changed
|
5
changelogs/unreleased/yogi-health-check-gl-card.yml
Normal file
5
changelogs/unreleased/yogi-health-check-gl-card.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Move to new GitLab UI for card in health check page
|
||||||
|
merge_request: 59081
|
||||||
|
author: Yogi (@yo)
|
||||||
|
type: changed
|
5
changelogs/unreleased/yogi-hide-templates-buttons.yml
Normal file
5
changelogs/unreleased/yogi-hide-templates-buttons.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove template tabs in new project page for GitLab.com
|
||||||
|
merge_request: 59083
|
||||||
|
author: Yogi (@yo)
|
||||||
|
type: changed
|
5
changelogs/unreleased/yogi-question-o-admin.yml
Normal file
5
changelogs/unreleased/yogi-question-o-admin.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Update to question-o and change color in admin users
|
||||||
|
merge_request: 59133
|
||||||
|
author: Yogi (@yo)
|
||||||
|
type: changed
|
5
changelogs/unreleased/yogi-question-o-navbar.yml
Normal file
5
changelogs/unreleased/yogi-question-o-navbar.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Update to question-o from question icon in navbar
|
||||||
|
merge_request: 59134
|
||||||
|
author: Yogi (@yo)
|
||||||
|
type: changed
|
|
@ -1037,7 +1037,7 @@ we felt we cannot assume this is true everywhere.
|
||||||
The cache needs a directory to store its files in. This directory
|
The cache needs a directory to store its files in. This directory
|
||||||
should be:
|
should be:
|
||||||
|
|
||||||
- In a filesystem with enough space. If the cache filesystem runs out of space, all
|
- In a file system with enough space. If the cache file system runs out of space, all
|
||||||
fetches start failing.
|
fetches start failing.
|
||||||
- On a disk with enough IO bandwidth. If the cache disk runs out of IO bandwidth, all
|
- On a disk with enough IO bandwidth. If the cache disk runs out of IO bandwidth, all
|
||||||
fetches, and probably the entire server, slows down.
|
fetches, and probably the entire server, slows down.
|
||||||
|
@ -1052,8 +1052,8 @@ uses a unique random string as part of the cache filenames it creates. This mean
|
||||||
- They do not reuse another process's files.
|
- They do not reuse another process's files.
|
||||||
|
|
||||||
While the default directory puts the cache files in the same
|
While the default directory puts the cache files in the same
|
||||||
filesystem as your repository data, this is not requirement. You can
|
file system as your repository data, this is not requirement. You can
|
||||||
put the cache files on a different filesystem if that works better for
|
put the cache files on a different file system if that works better for
|
||||||
your infrastructure.
|
your infrastructure.
|
||||||
|
|
||||||
The amount of IO bandwidth required from the disk depends on:
|
The amount of IO bandwidth required from the disk depends on:
|
||||||
|
@ -1157,7 +1157,7 @@ The following cache metrics are available.
|
||||||
|
|
||||||
|Metric|Type|Labels|Description|
|
|Metric|Type|Labels|Description|
|
||||||
|:---|:---|:---|:---|
|
|:---|:---|:---|:---|
|
||||||
|`gitaly_pack_objects_cache_enabled`|gauge|`dir`,`max_age`|Set to `1` when the cache is enabled via the Gitaly config file|
|
|`gitaly_pack_objects_cache_enabled`|gauge|`dir`,`max_age`|Set to `1` when the cache is enabled via the Gitaly configuration file|
|
||||||
|`gitaly_pack_objects_cache_lookups_total`|counter|`result`|Hit/miss counter for cache lookups|
|
|`gitaly_pack_objects_cache_lookups_total`|counter|`result`|Hit/miss counter for cache lookups|
|
||||||
|`gitaly_pack_objects_generated_bytes_total`|counter||Number of bytes written into the cache|
|
|`gitaly_pack_objects_generated_bytes_total`|counter||Number of bytes written into the cache|
|
||||||
|`gitaly_pack_objects_served_bytes_total`|counter||Number of bytes read from the cache|
|
|`gitaly_pack_objects_served_bytes_total`|counter||Number of bytes read from the cache|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
Before Width: | Height: | Size: 31 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
|
@ -770,6 +770,29 @@ argument :title, GraphQL::STRING_TYPE,
|
||||||
description: copy_field_description(Types::MergeRequestType, :title)
|
description: copy_field_description(Types::MergeRequestType, :title)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Documentation references
|
||||||
|
|
||||||
|
Sometimes we want to refer to external URLs in our descriptions. To make this
|
||||||
|
easier, and provide proper markup in the generated reference documentation, we
|
||||||
|
provide a `see` property on fields. For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
field :genus,
|
||||||
|
type: GraphQL::STRING_TYPE,
|
||||||
|
null: true,
|
||||||
|
description: 'A taxonomic genus.'
|
||||||
|
see: { 'Wikipedia page on genera' => 'https://wikipedia.org/wiki/Genus' }
|
||||||
|
```
|
||||||
|
|
||||||
|
This will render in our documentation as:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
A taxonomic genus. See: [Wikipedia page on genera](https://wikipedia.org/wiki/Genus)
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple documentation references can be provided. The syntax for this property
|
||||||
|
is a `HashMap` where the keys are textual descriptions, and the values are URLs.
|
||||||
|
|
||||||
## Authorization
|
## Authorization
|
||||||
|
|
||||||
See: [GraphQL Authorization](graphql_guide/authorization.md)
|
See: [GraphQL Authorization](graphql_guide/authorization.md)
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 31 KiB |
|
@ -52,7 +52,7 @@ The following table shows the supported metrics, at which level they are support
|
||||||
| --------------- | ----------- | --------------- | ---------- | ------- |
|
| --------------- | ----------- | --------------- | ---------- | ------- |
|
||||||
| `deployment_frequency` | Project-level | [13.7+](../../api/dora/metrics.md) | [13.8+](#deployment-frequency-charts) | The [old API endopint](../../api/dora4_project_analytics.md) was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/323713) in 13.10. |
|
| `deployment_frequency` | Project-level | [13.7+](../../api/dora/metrics.md) | [13.8+](#deployment-frequency-charts) | The [old API endopint](../../api/dora4_project_analytics.md) was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/323713) in 13.10. |
|
||||||
| `deployment_frequency` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | |
|
| `deployment_frequency` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | |
|
||||||
| `lead_time_for_changes` | Project-level | [13.10+](../../api/dora/metrics.md) | To be supported | Unit in seconds. Aggregation method is median. |
|
| `lead_time_for_changes` | Project-level | [13.10+](../../api/dora/metrics.md) | [13.11+](#lead-time-charts) | Unit in seconds. Aggregation method is median. |
|
||||||
| `lead_time_for_changes` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | Unit in seconds. Aggregation method is median. |
|
| `lead_time_for_changes` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | Unit in seconds. Aggregation method is median. |
|
||||||
| `change_failure_rate` | Project/Group-level | To be supported | To be supported | |
|
| `change_failure_rate` | Project/Group-level | To be supported | To be supported | |
|
||||||
| `time_to_restore_service` | Project/Group-level | To be supported | To be supported | |
|
| `time_to_restore_service` | Project/Group-level | To be supported | To be supported | |
|
||||||
|
@ -61,9 +61,10 @@ The following table shows the supported metrics, at which level they are support
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/275991) in GitLab 13.8.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/275991) in GitLab 13.8.
|
||||||
|
|
||||||
The **Analytics > CI/CD Analytics** page shows information about the deployment frequency to the
|
The **Analytics > CI/CD Analytics** page shows information about the deployment
|
||||||
`production` environment. The environment **must** be named `production` for its deployment
|
frequency to the `production` environment. The environment must be part of the
|
||||||
information to appear on the graphs.
|
[production deployment tier](../../ci/environments/index.md#deployment-tier-of-environments)
|
||||||
|
for its deployment information to appear on the graphs.
|
||||||
|
|
||||||
![Deployment frequency](img/deployment_frequency_chart_v13_8.png)
|
![Deployment frequency](img/deployment_frequency_chart_v13_8.png)
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 80 KiB |
|
@ -6,25 +6,32 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
# Webex Teams service **(FREE)**
|
# Webex Teams service **(FREE)**
|
||||||
|
|
||||||
You can configure GitLab to send notifications to a Webex Teams space.
|
You can configure GitLab to send notifications to a Webex Teams space:
|
||||||
|
|
||||||
|
1. Create a webhook for the space.
|
||||||
|
1. Add the webhook to GitLab.
|
||||||
|
|
||||||
## Create a webhook for the space
|
## Create a webhook for the space
|
||||||
|
|
||||||
1. Go to the [Incoming Webhooks app page](https://apphub.webex.com/messaging/applications/incoming-webhooks-cisco-systems-38054).
|
1. Go to the [Incoming Webhooks app page](https://apphub.webex.com/messaging/applications/incoming-webhooks-cisco-systems-38054).
|
||||||
1. Click **Connect** and log in to Webex Teams, if required.
|
1. Select **Connect** and log in to Webex Teams, if required.
|
||||||
1. Enter a name for the webhook and select the space to receive the notifications.
|
1. Enter a name for the webhook and select the space to receive the notifications.
|
||||||
1. Click **ADD**.
|
1. Select **ADD**.
|
||||||
1. Copy the **Webhook URL**.
|
1. Copy the **Webhook URL**.
|
||||||
|
|
||||||
## Configure settings in GitLab
|
## Configure settings in GitLab
|
||||||
|
|
||||||
Once you have a webhook URL for your Webex Teams space, you can configure GitLab to send notifications.
|
Once you have a webhook URL for your Webex Teams space, you can configure GitLab to send
|
||||||
|
notifications:
|
||||||
|
|
||||||
1. Navigate to **Project > Settings > Integrations**.
|
1. Navigate to:
|
||||||
|
- **Settings > Integrations** in a project to enable the integration at the project level.
|
||||||
|
- **Settings > Integrations** in a group to enable the integration at the group level.
|
||||||
|
- **Settings > Integrations** in the Admin Area (**{admin}**) to enable an instance-level integration.
|
||||||
1. Select the **Webex Teams** integration.
|
1. Select the **Webex Teams** integration.
|
||||||
1. Ensure that the **Active** toggle is enabled.
|
1. Ensure that the **Active** toggle is enabled.
|
||||||
1. Select the checkboxes corresponding to the GitLab events you want to receive in Webex Teams.
|
1. Select the checkboxes corresponding to the GitLab events you want to receive in Webex Teams.
|
||||||
1. Paste the **Webhook** URL for the Webex Teams space.
|
1. Paste the **Webhook** URL for the Webex Teams space.
|
||||||
1. Configure the remaining options and then click **Test settings and save changes**.
|
1. Configure the remaining options and then click **Test settings and save changes**.
|
||||||
|
|
||||||
The Webex Teams space now begins to receive all applicable GitLab events.
|
The Webex Teams space begins to receive all applicable GitLab events.
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 29 KiB |
|
@ -765,7 +765,7 @@ module API
|
||||||
required: true,
|
required: true,
|
||||||
name: :webhook,
|
name: :webhook,
|
||||||
type: String,
|
type: String,
|
||||||
desc: 'The Webex Teams webhook. e.g. https://api.ciscospark.com/v1/webhooks/incoming/…'
|
desc: 'The Webex Teams webhook. For example, https://api.ciscospark.com/v1/webhooks/incoming/...'
|
||||||
},
|
},
|
||||||
chat_notification_events
|
chat_notification_events
|
||||||
].flatten
|
].flatten
|
||||||
|
|
|
@ -9956,10 +9956,7 @@ msgstr ""
|
||||||
msgid "DORA4Metrics|Days from merge to deploy"
|
msgid "DORA4Metrics|Days from merge to deploy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DORA4Metrics|Deployments"
|
msgid "DORA4Metrics|Deployment frequency"
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "DORA4Metrics|Deployments charts"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DORA4Metrics|Lead time"
|
msgid "DORA4Metrics|Lead time"
|
||||||
|
@ -9971,19 +9968,19 @@ msgstr ""
|
||||||
msgid "DORA4Metrics|No merge requests were deployed during this period"
|
msgid "DORA4Metrics|No merge requests were deployed during this period"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DORA4Metrics|Something went wrong while getting deployment frequency data"
|
msgid "DORA4Metrics|Number of deployments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "DORA4Metrics|Something went wrong while getting deployment frequency data."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DORA4Metrics|Something went wrong while getting lead time data."
|
msgid "DORA4Metrics|Something went wrong while getting lead time data."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DORA4Metrics|These charts display the frequency of deployments to the production environment, as part of the DORA 4 metrics. The environment must be named %{codeStart}production%{codeEnd} for its data to appear in these charts."
|
msgid "DORA4Metrics|The chart displays the frequency of deployments to production environment(s) that are based on the %{linkStart}deployment_tier%{linkEnd} value."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DORA4Metrics|These charts display the median time between a merge request being merged and deployed to production, as part of the DORA 4 metrics."
|
msgid "DORA4Metrics|The chart displays the median time between a merge request being merged and deployed to production environment(s) that are based on the %{linkStart}deployment_tier%{linkEnd} value."
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "DORA4|Lead time charts"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
|
@ -10990,6 +10987,9 @@ msgstr ""
|
||||||
msgid "Deployment Frequency"
|
msgid "Deployment Frequency"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Deployment frequency"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Deployments"
|
msgid "Deployments"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -18953,6 +18953,9 @@ msgstr ""
|
||||||
msgid "Lead Time"
|
msgid "Lead Time"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Lead time"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Learn CI/CD syntax"
|
msgid "Learn CI/CD syntax"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -35808,6 +35811,15 @@ msgstr ""
|
||||||
msgid "WebIDE|You need permission to edit files directly in this project. Go to your fork to make changes and submit a merge request."
|
msgid "WebIDE|You need permission to edit files directly in this project. Go to your fork to make changes and submit a merge request."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "WebexTeamsService|Send notifications about project events to Webex Teams."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "WebexTeamsService|Send notifications about project events to a Webex Teams conversation. %{docs_link}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "WebexTeamsService|Webex Teams"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Webhook"
|
msgid "Webhook"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ function run_locally_or_in_docker() {
|
||||||
$cmd $args
|
$cmd $args
|
||||||
elif hash docker 2>/dev/null
|
elif hash docker 2>/dev/null
|
||||||
then
|
then
|
||||||
docker run -t -v ${PWD}:/gitlab -w /gitlab --rm registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.12-vale-2.6.1-markdownlint-0.24.0 ${cmd} ${args}
|
docker run -t -v ${PWD}:/gitlab -w /gitlab --rm registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.13-vale-2.10.2-markdownlint-0.26.0 ${cmd} ${args}
|
||||||
else
|
else
|
||||||
echo
|
echo
|
||||||
echo " ✖ ERROR: '${cmd}' not found. Install '${cmd}' or Docker to proceed." >&2
|
echo " ✖ ERROR: '${cmd}' not found. Install '${cmd}' or Docker to proceed." >&2
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Projects::HooksController do
|
RSpec.describe Projects::HooksController do
|
||||||
let(:project) { create(:project) }
|
let_it_be(:project) { create(:project) }
|
||||||
let(:user) { create(:user) }
|
|
||||||
|
let(:user) { project.owner }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.add_maintainer(user)
|
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -20,6 +20,56 @@ RSpec.describe Projects::HooksController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#edit' do
|
||||||
|
let_it_be(:hook) { create(:project_hook, project: project) }
|
||||||
|
|
||||||
|
let(:params) do
|
||||||
|
{ namespace_id: project.namespace, project_id: project, id: hook.id }
|
||||||
|
end
|
||||||
|
|
||||||
|
render_views
|
||||||
|
|
||||||
|
it 'does not error if the hook cannot be found' do
|
||||||
|
get :edit, params: params.merge(id: non_existing_record_id)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'assigns hook_logs' do
|
||||||
|
get :edit, params: params
|
||||||
|
|
||||||
|
expect(assigns[:hook]).to be_present
|
||||||
|
expect(assigns[:hook_logs]).to be_empty
|
||||||
|
it_renders_correctly
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles when logs are present' do
|
||||||
|
create_list(:web_hook_log, 3, web_hook: hook)
|
||||||
|
|
||||||
|
get :edit, params: params
|
||||||
|
|
||||||
|
expect(assigns[:hook]).to be_present
|
||||||
|
expect(assigns[:hook_logs].count).to eq 3
|
||||||
|
it_renders_correctly
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can paginate logs' do
|
||||||
|
create_list(:web_hook_log, 21, web_hook: hook)
|
||||||
|
|
||||||
|
get :edit, params: params.merge(page: 2)
|
||||||
|
|
||||||
|
expect(assigns[:hook]).to be_present
|
||||||
|
expect(assigns[:hook_logs].count).to eq 1
|
||||||
|
it_renders_correctly
|
||||||
|
end
|
||||||
|
|
||||||
|
def it_renders_correctly
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(response).to render_template(:edit)
|
||||||
|
expect(response).to render_template('projects/hook_logs/_index')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#create' do
|
describe '#create' do
|
||||||
it 'sets all parameters' do
|
it 'sets all parameters' do
|
||||||
hook_params = {
|
hook_params = {
|
||||||
|
|
|
@ -586,13 +586,15 @@ RSpec.describe Projects::IssuesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'PUT #update' do
|
describe 'PUT #update' do
|
||||||
|
let(:issue_params) { { title: 'New title' } }
|
||||||
|
|
||||||
subject do
|
subject do
|
||||||
put :update,
|
put :update,
|
||||||
params: {
|
params: {
|
||||||
namespace_id: project.namespace,
|
namespace_id: project.namespace,
|
||||||
project_id: project,
|
project_id: project,
|
||||||
id: issue.to_param,
|
id: issue.to_param,
|
||||||
issue: { title: 'New title' }
|
issue: issue_params
|
||||||
},
|
},
|
||||||
format: :json
|
format: :json
|
||||||
end
|
end
|
||||||
|
@ -614,6 +616,17 @@ RSpec.describe Projects::IssuesController do
|
||||||
expect(issue.reload.title).to eq('New title')
|
expect(issue.reload.title).to eq('New title')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with issue_type param' do
|
||||||
|
let(:issue_params) { { issue_type: 'incident' } }
|
||||||
|
|
||||||
|
it 'permits the parameter' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(issue.reload.issue_type).to eql('incident')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the SpamVerdictService disallows' do
|
context 'when the SpamVerdictService disallows' do
|
||||||
before do
|
before do
|
||||||
stub_application_setting(recaptcha_enabled: true)
|
stub_application_setting(recaptcha_enabled: true)
|
||||||
|
|
|
@ -40,7 +40,7 @@ RSpec.describe 'List issue resource label events', :js do
|
||||||
|
|
||||||
labels.each { |label| click_link label }
|
labels.each { |label| click_link label }
|
||||||
|
|
||||||
click_on 'Edit'
|
send_keys(:escape)
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -54,8 +54,8 @@ describe('ProjectsPipelinesChartsApp', () => {
|
||||||
expect(findGlTabs().exists()).toBe(true);
|
expect(findGlTabs().exists()).toBe(true);
|
||||||
|
|
||||||
expect(findGlTabAtIndex(0).attributes('title')).toBe('Pipelines');
|
expect(findGlTabAtIndex(0).attributes('title')).toBe('Pipelines');
|
||||||
expect(findGlTabAtIndex(1).attributes('title')).toBe('Deployments');
|
expect(findGlTabAtIndex(1).attributes('title')).toBe('Deployment frequency');
|
||||||
expect(findGlTabAtIndex(2).attributes('title')).toBe('Lead Time');
|
expect(findGlTabAtIndex(2).attributes('title')).toBe('Lead time');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the pipeline charts', () => {
|
it('renders the pipeline charts', () => {
|
||||||
|
@ -75,7 +75,7 @@ describe('ProjectsPipelinesChartsApp', () => {
|
||||||
setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`);
|
setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`);
|
||||||
|
|
||||||
mergeUrlParams.mockImplementation(({ chart }, path) => {
|
mergeUrlParams.mockImplementation(({ chart }, path) => {
|
||||||
expect(chart).toBe('deployments');
|
expect(chart).toBe('deployment-frequency');
|
||||||
expect(path).toBe(window.location.pathname);
|
expect(path).toBe(window.location.pathname);
|
||||||
chartsPath = `${path}?chart=${chart}`;
|
chartsPath = `${path}?chart=${chart}`;
|
||||||
return chartsPath;
|
return chartsPath;
|
||||||
|
@ -114,12 +114,12 @@ describe('ProjectsPipelinesChartsApp', () => {
|
||||||
|
|
||||||
describe('when provided with a query param', () => {
|
describe('when provided with a query param', () => {
|
||||||
it.each`
|
it.each`
|
||||||
chart | tab
|
chart | tab
|
||||||
${'lead-time'} | ${'2'}
|
${'lead-time'} | ${'2'}
|
||||||
${'deployments'} | ${'1'}
|
${'deployment-frequency'} | ${'1'}
|
||||||
${'pipelines'} | ${'0'}
|
${'pipelines'} | ${'0'}
|
||||||
${'fake'} | ${'0'}
|
${'fake'} | ${'0'}
|
||||||
${''} | ${'0'}
|
${''} | ${'0'}
|
||||||
`('shows the correct tab for URL parameter "$chart"', ({ chart, tab }) => {
|
`('shows the correct tab for URL parameter "$chart"', ({ chart, tab }) => {
|
||||||
setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts?chart=${chart}`);
|
setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts?chart=${chart}`);
|
||||||
getParameterValues.mockImplementation((name) => {
|
getParameterValues.mockImplementation((name) => {
|
||||||
|
@ -152,7 +152,7 @@ describe('ProjectsPipelinesChartsApp', () => {
|
||||||
|
|
||||||
getParameterValues.mockImplementationOnce((name) => {
|
getParameterValues.mockImplementationOnce((name) => {
|
||||||
expect(name).toBe('chart');
|
expect(name).toBe('chart');
|
||||||
return ['deployments'];
|
return ['deployment-frequency'];
|
||||||
});
|
});
|
||||||
|
|
||||||
popstateHandler();
|
popstateHandler();
|
||||||
|
|
|
@ -10,29 +10,35 @@ const TEST_PROJECT_ID = '1234';
|
||||||
const TEST_CREATE_FROM = 'test-create-from';
|
const TEST_CREATE_FROM = 'test-create-from';
|
||||||
const NONEXISTENT_TAG_NAME = 'nonexistent-tag';
|
const NONEXISTENT_TAG_NAME = 'nonexistent-tag';
|
||||||
|
|
||||||
// A mock version of the RefSelector component that simulates
|
|
||||||
// a scenario where the users has searched for "nonexistent-tag"
|
|
||||||
// and the component has found no tags that match.
|
|
||||||
const RefSelectorStub = Vue.component('RefSelectorStub', {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
footerSlotProps: {
|
|
||||||
isLoading: false,
|
|
||||||
matches: {
|
|
||||||
tags: { totalCount: 0 },
|
|
||||||
},
|
|
||||||
query: NONEXISTENT_TAG_NAME,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
template: '<div><slot name="footer" v-bind="footerSlotProps"></slot></div>',
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('releases/components/tag_field_new', () => {
|
describe('releases/components/tag_field_new', () => {
|
||||||
let store;
|
let store;
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let RefSelectorStub;
|
||||||
|
|
||||||
|
const createComponent = (
|
||||||
|
mountFn = shallowMount,
|
||||||
|
{ searchQuery } = { searchQuery: NONEXISTENT_TAG_NAME },
|
||||||
|
) => {
|
||||||
|
// A mock version of the RefSelector component that just renders the
|
||||||
|
// #footer slot, so that the content inside this slot can be tested.
|
||||||
|
RefSelectorStub = Vue.component('RefSelectorStub', {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
footerSlotProps: {
|
||||||
|
isLoading: false,
|
||||||
|
matches: {
|
||||||
|
tags: {
|
||||||
|
totalCount: 1,
|
||||||
|
list: [{ name: TEST_TAG_NAME }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
query: searchQuery,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: '<div><slot name="footer" v-bind="footerSlotProps"></slot></div>',
|
||||||
|
});
|
||||||
|
|
||||||
const createComponent = (mountFn = shallowMount) => {
|
|
||||||
wrapper = mountFn(TagFieldNew, {
|
wrapper = mountFn(TagFieldNew, {
|
||||||
store,
|
store,
|
||||||
stubs: {
|
stubs: {
|
||||||
|
@ -84,8 +90,6 @@ describe('releases/components/tag_field_new', () => {
|
||||||
describe('when the user selects a new tag name', () => {
|
describe('when the user selects a new tag name', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
findCreateNewTagOption().vm.$emit('click');
|
findCreateNewTagOption().vm.$emit('click');
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates the store's release.tagName property", () => {
|
it("updates the store's release.tagName property", () => {
|
||||||
|
@ -102,8 +106,6 @@ describe('releases/components/tag_field_new', () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
findTagNameDropdown().vm.$emit('input', updatedTagName);
|
findTagNameDropdown().vm.$emit('input', updatedTagName);
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates the store's release.tagName property", () => {
|
it("updates the store's release.tagName property", () => {
|
||||||
|
@ -116,6 +118,28 @@ describe('releases/components/tag_field_new', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('"Create tag" option', () => {
|
||||||
|
describe('when the search query exactly matches one of the search results', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
createComponent(mount, { searchQuery: TEST_TAG_NAME });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not show the "Create tag" option', () => {
|
||||||
|
expect(findCreateNewTagOption().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the search query does not exactly match one of the search results', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
createComponent(mount, { searchQuery: NONEXISTENT_TAG_NAME });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the "Create tag" option', () => {
|
||||||
|
expect(findCreateNewTagOption().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('validation', () => {
|
describe('validation', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createComponent(mount);
|
createComponent(mount);
|
||||||
|
@ -176,8 +200,6 @@ describe('releases/components/tag_field_new', () => {
|
||||||
const updatedCreateFrom = 'update-create-from';
|
const updatedCreateFrom = 'update-create-from';
|
||||||
findCreateFromDropdown().vm.$emit('input', updatedCreateFrom);
|
findCreateFromDropdown().vm.$emit('input', updatedCreateFrom);
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
|
||||||
|
|
||||||
expect(store.state.editNew.createFrom).toBe(updatedCreateFrom);
|
expect(store.state.editNew.createFrom).toBe(updatedCreateFrom);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Vuex from 'vuex';
|
||||||
import CodequalityIssueBody from '~/reports/codequality_report/components/codequality_issue_body.vue';
|
import CodequalityIssueBody from '~/reports/codequality_report/components/codequality_issue_body.vue';
|
||||||
import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue';
|
import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue';
|
||||||
import { getStoreConfig } from '~/reports/codequality_report/store';
|
import { getStoreConfig } from '~/reports/codequality_report/store';
|
||||||
import { mockParsedHeadIssues, mockParsedBaseIssues } from './mock_data';
|
import { parsedReportIssues } from './mock_data';
|
||||||
|
|
||||||
const localVue = createLocalVue();
|
const localVue = createLocalVue();
|
||||||
localVue.use(Vuex);
|
localVue.use(Vuex);
|
||||||
|
@ -80,7 +80,7 @@ describe('Grouped code quality reports app', () => {
|
||||||
describe('with issues', () => {
|
describe('with issues', () => {
|
||||||
describe('with new issues', () => {
|
describe('with new issues', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockStore.state.newIssues = [mockParsedHeadIssues[0]];
|
mockStore.state.newIssues = parsedReportIssues.newIssues;
|
||||||
mockStore.state.resolvedIssues = [];
|
mockStore.state.resolvedIssues = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -89,14 +89,14 @@ describe('Grouped code quality reports app', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders custom codequality issue body', () => {
|
it('renders custom codequality issue body', () => {
|
||||||
expect(findIssueBody().props('issue')).toEqual(mockParsedHeadIssues[0]);
|
expect(findIssueBody().props('issue')).toEqual(parsedReportIssues.newIssues[0]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with resolved issues', () => {
|
describe('with resolved issues', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockStore.state.newIssues = [];
|
mockStore.state.newIssues = [];
|
||||||
mockStore.state.resolvedIssues = [mockParsedBaseIssues[0]];
|
mockStore.state.resolvedIssues = parsedReportIssues.resolvedIssues;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders summary text', () => {
|
it('renders summary text', () => {
|
||||||
|
@ -104,14 +104,14 @@ describe('Grouped code quality reports app', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders custom codequality issue body', () => {
|
it('renders custom codequality issue body', () => {
|
||||||
expect(findIssueBody().props('issue')).toEqual(mockParsedBaseIssues[0]);
|
expect(findIssueBody().props('issue')).toEqual(parsedReportIssues.resolvedIssues[0]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with new and resolved issues', () => {
|
describe('with new and resolved issues', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockStore.state.newIssues = [mockParsedHeadIssues[0]];
|
mockStore.state.newIssues = parsedReportIssues.newIssues;
|
||||||
mockStore.state.resolvedIssues = [mockParsedBaseIssues[0]];
|
mockStore.state.resolvedIssues = parsedReportIssues.resolvedIssues;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders summary text', () => {
|
it('renders summary text', () => {
|
||||||
|
@ -121,7 +121,7 @@ describe('Grouped code quality reports app', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders custom codequality issue body', () => {
|
it('renders custom codequality issue body', () => {
|
||||||
expect(findIssueBody().props('issue')).toEqual(mockParsedHeadIssues[0]);
|
expect(findIssueBody().props('issue')).toEqual(parsedReportIssues.newIssues[0]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,94 +1,3 @@
|
||||||
export const headIssues = [
|
|
||||||
{
|
|
||||||
check_name: 'Rubocop/Lint/UselessAssignment',
|
|
||||||
description: 'Insecure Dependency',
|
|
||||||
location: {
|
|
||||||
path: 'lib/six.rb',
|
|
||||||
lines: {
|
|
||||||
begin: 6,
|
|
||||||
end: 7,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fingerprint: 'e879dd9bbc0953cad5037cde7ff0f627',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
categories: ['Security'],
|
|
||||||
check_name: 'Insecure Dependency',
|
|
||||||
description: 'Insecure Dependency',
|
|
||||||
location: {
|
|
||||||
path: 'Gemfile.lock',
|
|
||||||
lines: {
|
|
||||||
begin: 22,
|
|
||||||
end: 22,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const mockParsedHeadIssues = [
|
|
||||||
{
|
|
||||||
...headIssues[1],
|
|
||||||
name: 'Insecure Dependency',
|
|
||||||
path: 'lib/six.rb',
|
|
||||||
urlPath: 'headPath/lib/six.rb#L6',
|
|
||||||
line: 6,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const baseIssues = [
|
|
||||||
{
|
|
||||||
categories: ['Security'],
|
|
||||||
check_name: 'Insecure Dependency',
|
|
||||||
description: 'Insecure Dependency',
|
|
||||||
location: {
|
|
||||||
path: 'Gemfile.lock',
|
|
||||||
lines: {
|
|
||||||
begin: 22,
|
|
||||||
end: 22,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
categories: ['Security'],
|
|
||||||
check_name: 'Insecure Dependency',
|
|
||||||
description: 'Insecure Dependency',
|
|
||||||
location: {
|
|
||||||
path: 'Gemfile.lock',
|
|
||||||
lines: {
|
|
||||||
begin: 21,
|
|
||||||
end: 21,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fingerprint: 'ca2354534dee94ae60ba2f54e3857c50e5',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const mockParsedBaseIssues = [
|
|
||||||
{
|
|
||||||
...baseIssues[1],
|
|
||||||
name: 'Insecure Dependency',
|
|
||||||
path: 'Gemfile.lock',
|
|
||||||
line: 21,
|
|
||||||
urlPath: 'basePath/Gemfile.lock#L21',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const issueDiff = [
|
|
||||||
{
|
|
||||||
categories: ['Security'],
|
|
||||||
check_name: 'Insecure Dependency',
|
|
||||||
description: 'Insecure Dependency',
|
|
||||||
fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
|
|
||||||
line: 6,
|
|
||||||
location: { lines: { begin: 22, end: 22 }, path: 'Gemfile.lock' },
|
|
||||||
name: 'Insecure Dependency',
|
|
||||||
path: 'lib/six.rb',
|
|
||||||
urlPath: 'headPath/lib/six.rb#L6',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const reportIssues = {
|
export const reportIssues = {
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
new_errors: [
|
new_errors: [
|
||||||
|
|
|
@ -5,30 +5,7 @@ import axios from '~/lib/utils/axios_utils';
|
||||||
import createStore from '~/reports/codequality_report/store';
|
import createStore from '~/reports/codequality_report/store';
|
||||||
import * as actions from '~/reports/codequality_report/store/actions';
|
import * as actions from '~/reports/codequality_report/store/actions';
|
||||||
import * as types from '~/reports/codequality_report/store/mutation_types';
|
import * as types from '~/reports/codequality_report/store/mutation_types';
|
||||||
import {
|
import { reportIssues, parsedReportIssues } from '../mock_data';
|
||||||
headIssues,
|
|
||||||
baseIssues,
|
|
||||||
mockParsedHeadIssues,
|
|
||||||
mockParsedBaseIssues,
|
|
||||||
reportIssues,
|
|
||||||
parsedReportIssues,
|
|
||||||
} from '../mock_data';
|
|
||||||
|
|
||||||
// mock codequality comparison worker
|
|
||||||
jest.mock('~/reports/codequality_report/workers/codequality_comparison_worker', () =>
|
|
||||||
jest.fn().mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
addEventListener: (eventName, callback) => {
|
|
||||||
callback({
|
|
||||||
data: {
|
|
||||||
newIssues: [mockParsedHeadIssues[0]],
|
|
||||||
resolvedIssues: [mockParsedBaseIssues[0]],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('Codequality Reports actions', () => {
|
describe('Codequality Reports actions', () => {
|
||||||
let localState;
|
let localState;
|
||||||
|
@ -43,9 +20,6 @@ describe('Codequality Reports actions', () => {
|
||||||
it('should commit SET_PATHS mutation', (done) => {
|
it('should commit SET_PATHS mutation', (done) => {
|
||||||
const paths = {
|
const paths = {
|
||||||
basePath: 'basePath',
|
basePath: 'basePath',
|
||||||
headPath: 'headPath',
|
|
||||||
baseBlobPath: 'baseBlobPath',
|
|
||||||
headBlobPath: 'headBlobPath',
|
|
||||||
reportsPath: 'reportsPath',
|
reportsPath: 'reportsPath',
|
||||||
helpPath: 'codequalityHelpPath',
|
helpPath: 'codequalityHelpPath',
|
||||||
};
|
};
|
||||||
|
@ -63,119 +37,64 @@ describe('Codequality Reports actions', () => {
|
||||||
|
|
||||||
describe('fetchReports', () => {
|
describe('fetchReports', () => {
|
||||||
let mock;
|
let mock;
|
||||||
let diffFeatureFlagEnabled;
|
|
||||||
|
|
||||||
describe('with codequalityBackendComparison feature flag enabled', () => {
|
beforeEach(() => {
|
||||||
beforeEach(() => {
|
localState.reportsPath = `${TEST_HOST}/codequality_reports.json`;
|
||||||
diffFeatureFlagEnabled = true;
|
localState.basePath = '/base/path';
|
||||||
localState.reportsPath = `${TEST_HOST}/codequality_reports.json`;
|
mock = new MockAdapter(axios);
|
||||||
mock = new MockAdapter(axios);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mock.restore();
|
mock.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('on success', () => {
|
describe('on success', () => {
|
||||||
it('commits REQUEST_REPORTS and dispatches receiveReportsSuccess', (done) => {
|
it('commits REQUEST_REPORTS and dispatches receiveReportsSuccess', (done) => {
|
||||||
mock.onGet(`${TEST_HOST}/codequality_reports.json`).reply(200, reportIssues);
|
mock.onGet(`${TEST_HOST}/codequality_reports.json`).reply(200, reportIssues);
|
||||||
|
|
||||||
testAction(
|
testAction(
|
||||||
actions.fetchReports,
|
actions.fetchReports,
|
||||||
diffFeatureFlagEnabled,
|
null,
|
||||||
localState,
|
localState,
|
||||||
[{ type: types.REQUEST_REPORTS }],
|
[{ type: types.REQUEST_REPORTS }],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
payload: parsedReportIssues,
|
payload: parsedReportIssues,
|
||||||
type: 'receiveReportsSuccess',
|
type: 'receiveReportsSuccess',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
done,
|
done,
|
||||||
);
|
);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('on error', () => {
|
|
||||||
it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
|
|
||||||
mock.onGet(`${TEST_HOST}/codequality_reports.json`).reply(500);
|
|
||||||
|
|
||||||
testAction(
|
|
||||||
actions.fetchReports,
|
|
||||||
diffFeatureFlagEnabled,
|
|
||||||
localState,
|
|
||||||
[{ type: types.REQUEST_REPORTS }],
|
|
||||||
[{ type: 'receiveReportsError', payload: expect.any(Error) }],
|
|
||||||
done,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with codequalityBackendComparison feature flag disabled', () => {
|
describe('on error', () => {
|
||||||
beforeEach(() => {
|
it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
|
||||||
diffFeatureFlagEnabled = false;
|
mock.onGet(`${TEST_HOST}/codequality_reports.json`).reply(500);
|
||||||
localState.headPath = `${TEST_HOST}/head.json`;
|
|
||||||
localState.basePath = `${TEST_HOST}/base.json`;
|
testAction(
|
||||||
mock = new MockAdapter(axios);
|
actions.fetchReports,
|
||||||
|
null,
|
||||||
|
localState,
|
||||||
|
[{ type: types.REQUEST_REPORTS }],
|
||||||
|
[{ type: 'receiveReportsError', payload: expect.any(Error) }],
|
||||||
|
done,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
describe('with no base path', () => {
|
||||||
mock.restore();
|
it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
|
||||||
});
|
localState.basePath = null;
|
||||||
|
|
||||||
describe('on success', () => {
|
testAction(
|
||||||
it('commits REQUEST_REPORTS and dispatches receiveReportsSuccess', (done) => {
|
actions.fetchReports,
|
||||||
mock.onGet(`${TEST_HOST}/head.json`).reply(200, headIssues);
|
null,
|
||||||
mock.onGet(`${TEST_HOST}/base.json`).reply(200, baseIssues);
|
localState,
|
||||||
|
[{ type: types.REQUEST_REPORTS }],
|
||||||
testAction(
|
[{ type: 'receiveReportsError' }],
|
||||||
actions.fetchReports,
|
done,
|
||||||
diffFeatureFlagEnabled,
|
);
|
||||||
localState,
|
|
||||||
[{ type: types.REQUEST_REPORTS }],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
payload: {
|
|
||||||
newIssues: [mockParsedHeadIssues[0]],
|
|
||||||
resolvedIssues: [mockParsedBaseIssues[0]],
|
|
||||||
},
|
|
||||||
type: 'receiveReportsSuccess',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
done,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('on error', () => {
|
|
||||||
it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
|
|
||||||
mock.onGet(`${TEST_HOST}/head.json`).reply(500);
|
|
||||||
|
|
||||||
testAction(
|
|
||||||
actions.fetchReports,
|
|
||||||
diffFeatureFlagEnabled,
|
|
||||||
localState,
|
|
||||||
[{ type: types.REQUEST_REPORTS }],
|
|
||||||
[{ type: 'receiveReportsError', payload: expect.any(Error) }],
|
|
||||||
done,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with no base path', () => {
|
|
||||||
it('commits REQUEST_REPORTS and dispatches receiveReportsError', (done) => {
|
|
||||||
localState.basePath = null;
|
|
||||||
|
|
||||||
testAction(
|
|
||||||
actions.fetchReports,
|
|
||||||
diffFeatureFlagEnabled,
|
|
||||||
localState,
|
|
||||||
[{ type: types.REQUEST_REPORTS }],
|
|
||||||
[{ type: 'receiveReportsError' }],
|
|
||||||
done,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,23 +13,17 @@ describe('Codequality Reports mutations', () => {
|
||||||
describe('SET_PATHS', () => {
|
describe('SET_PATHS', () => {
|
||||||
it('sets paths to given values', () => {
|
it('sets paths to given values', () => {
|
||||||
const basePath = 'base.json';
|
const basePath = 'base.json';
|
||||||
const headPath = 'head.json';
|
const reportsPath = 'reports.json';
|
||||||
const baseBlobPath = 'base/blob/path/';
|
|
||||||
const headBlobPath = 'head/blob/path/';
|
|
||||||
const helpPath = 'help.html';
|
const helpPath = 'help.html';
|
||||||
|
|
||||||
mutations.SET_PATHS(localState, {
|
mutations.SET_PATHS(localState, {
|
||||||
basePath,
|
basePath,
|
||||||
headPath,
|
reportsPath,
|
||||||
baseBlobPath,
|
|
||||||
headBlobPath,
|
|
||||||
helpPath,
|
helpPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(localState.basePath).toEqual(basePath);
|
expect(localState.basePath).toEqual(basePath);
|
||||||
expect(localState.headPath).toEqual(headPath);
|
expect(localState.reportsPath).toEqual(reportsPath);
|
||||||
expect(localState.baseBlobPath).toEqual(baseBlobPath);
|
|
||||||
expect(localState.headBlobPath).toEqual(headBlobPath);
|
|
||||||
expect(localState.helpPath).toEqual(helpPath);
|
expect(localState.helpPath).toEqual(helpPath);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,153 +0,0 @@
|
||||||
import {
|
|
||||||
parseCodeclimateMetrics,
|
|
||||||
doCodeClimateComparison,
|
|
||||||
} from '~/reports/codequality_report/store/utils/codequality_comparison';
|
|
||||||
import {
|
|
||||||
baseIssues,
|
|
||||||
mockParsedHeadIssues,
|
|
||||||
mockParsedBaseIssues,
|
|
||||||
reportIssues,
|
|
||||||
parsedReportIssues,
|
|
||||||
} from '../../mock_data';
|
|
||||||
|
|
||||||
jest.mock('~/reports/codequality_report/workers/codequality_comparison_worker', () => {
|
|
||||||
let mockPostMessageCallback;
|
|
||||||
return jest.fn().mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
addEventListener: (_, callback) => {
|
|
||||||
mockPostMessageCallback = callback;
|
|
||||||
},
|
|
||||||
postMessage: (data) => {
|
|
||||||
if (!data.headIssues) return mockPostMessageCallback({ data: {} });
|
|
||||||
if (!data.baseIssues) throw new Error();
|
|
||||||
const key = 'fingerprint';
|
|
||||||
return mockPostMessageCallback({
|
|
||||||
data: {
|
|
||||||
newIssues: data.headIssues.filter(
|
|
||||||
(item) => !data.baseIssues.find((el) => el[key] === item[key]),
|
|
||||||
),
|
|
||||||
resolvedIssues: data.baseIssues.filter(
|
|
||||||
(item) => !data.headIssues.find((el) => el[key] === item[key]),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Codequality report store utils', () => {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
describe('parseCodeclimateMetrics', () => {
|
|
||||||
it('should parse the issues from codeclimate artifacts', () => {
|
|
||||||
[result] = parseCodeclimateMetrics(baseIssues, 'path');
|
|
||||||
|
|
||||||
expect(result.name).toEqual(baseIssues[0].check_name);
|
|
||||||
expect(result.path).toEqual(baseIssues[0].location.path);
|
|
||||||
expect(result.line).toEqual(baseIssues[0].location.lines.begin);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse the issues from backend codequality diff', () => {
|
|
||||||
[result] = parseCodeclimateMetrics(reportIssues.new_errors, 'path');
|
|
||||||
|
|
||||||
expect(result.name).toEqual(parsedReportIssues.newIssues[0].name);
|
|
||||||
expect(result.path).toEqual(parsedReportIssues.newIssues[0].path);
|
|
||||||
expect(result.line).toEqual(parsedReportIssues.newIssues[0].line);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when an issue has no location or path', () => {
|
|
||||||
const issue = { description: 'Insecure Dependency' };
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
[result] = parseCodeclimateMetrics([issue], 'path');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is parsed', () => {
|
|
||||||
expect(result.name).toEqual(issue.description);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when an issue has a path but no line', () => {
|
|
||||||
const issue = { description: 'Insecure Dependency', location: { path: 'Gemfile.lock' } };
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
[result] = parseCodeclimateMetrics([issue], 'path');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is parsed', () => {
|
|
||||||
expect(result.name).toEqual(issue.description);
|
|
||||||
expect(result.path).toEqual(issue.location.path);
|
|
||||||
expect(result.urlPath).toEqual(`path/${issue.location.path}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when an issue has a line nested in positions', () => {
|
|
||||||
const issue = {
|
|
||||||
description: 'Insecure Dependency',
|
|
||||||
location: {
|
|
||||||
path: 'Gemfile.lock',
|
|
||||||
positions: { begin: { line: 84 } },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
[result] = parseCodeclimateMetrics([issue], 'path');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is parsed', () => {
|
|
||||||
expect(result.name).toEqual(issue.description);
|
|
||||||
expect(result.path).toEqual(issue.location.path);
|
|
||||||
expect(result.urlPath).toEqual(
|
|
||||||
`path/${issue.location.path}#L${issue.location.positions.begin.line}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with an empty issue array', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
result = parseCodeclimateMetrics([], 'path');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns an empty array', () => {
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('doCodeClimateComparison', () => {
|
|
||||||
describe('when the comparison worker finds changed issues', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
result = await doCodeClimateComparison(mockParsedHeadIssues, mockParsedBaseIssues);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the new and resolved issues', () => {
|
|
||||||
expect(result.resolvedIssues[0]).toEqual(mockParsedBaseIssues[0]);
|
|
||||||
expect(result.newIssues[0]).toEqual(mockParsedHeadIssues[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the comparison worker finds no changed issues', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
result = await doCodeClimateComparison([], []);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the empty issue arrays', () => {
|
|
||||||
expect(result.newIssues).toEqual([]);
|
|
||||||
expect(result.resolvedIssues).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the comparison worker is given malformed data', () => {
|
|
||||||
it('rejects the promise', () => {
|
|
||||||
return expect(doCodeClimateComparison(null)).rejects.toEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the comparison worker encounters an error', () => {
|
|
||||||
it('rejects the promise and throws an error', () => {
|
|
||||||
return expect(doCodeClimateComparison([], null)).rejects.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { reportIssues, parsedReportIssues } from 'jest/reports/codequality_report/mock_data';
|
||||||
|
import { parseCodeclimateMetrics } from '~/reports/codequality_report/store/utils/codequality_parser';
|
||||||
|
|
||||||
|
describe('Codequality report store utils', () => {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
describe('parseCodeclimateMetrics', () => {
|
||||||
|
it('should parse the issues from backend codequality diff', () => {
|
||||||
|
[result] = parseCodeclimateMetrics(reportIssues.new_errors, 'path');
|
||||||
|
|
||||||
|
expect(result.name).toEqual(parsedReportIssues.newIssues[0].name);
|
||||||
|
expect(result.path).toEqual(parsedReportIssues.newIssues[0].path);
|
||||||
|
expect(result.line).toEqual(parsedReportIssues.newIssues[0].line);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when an issue has no location or path', () => {
|
||||||
|
const issue = { description: 'Insecure Dependency' };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
[result] = parseCodeclimateMetrics([issue], 'path');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is parsed', () => {
|
||||||
|
expect(result.name).toEqual(issue.description);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when an issue has a path but no line', () => {
|
||||||
|
const issue = { description: 'Insecure Dependency', location: { path: 'Gemfile.lock' } };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
[result] = parseCodeclimateMetrics([issue], 'path');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is parsed', () => {
|
||||||
|
expect(result.name).toEqual(issue.description);
|
||||||
|
expect(result.path).toEqual(issue.location.path);
|
||||||
|
expect(result.urlPath).toEqual(`path/${issue.location.path}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when an issue has a line nested in positions', () => {
|
||||||
|
const issue = {
|
||||||
|
description: 'Insecure Dependency',
|
||||||
|
location: {
|
||||||
|
path: 'Gemfile.lock',
|
||||||
|
positions: { begin: { line: 84 } },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
[result] = parseCodeclimateMetrics([issue], 'path');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is parsed', () => {
|
||||||
|
expect(result.name).toEqual(issue.description);
|
||||||
|
expect(result.path).toEqual(issue.location.path);
|
||||||
|
expect(result.urlPath).toEqual(
|
||||||
|
`path/${issue.location.path}#L${issue.location.positions.begin.line}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with an empty issue array', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
result = parseCodeclimateMetrics([], 'path');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty array', () => {
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,8 +1,8 @@
|
||||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
|
|
||||||
|
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
|
||||||
import DropdownContents from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue';
|
import DropdownContents from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue';
|
||||||
|
|
||||||
import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
|
import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
|
||||||
|
|
||||||
import { mockConfig } from './mock_data';
|
import { mockConfig } from './mock_data';
|
||||||
|
@ -50,13 +50,20 @@ describe('DropdownContent', () => {
|
||||||
describe('template', () => {
|
describe('template', () => {
|
||||||
it('renders component container element with class `labels-select-dropdown-contents` and no styles', () => {
|
it('renders component container element with class `labels-select-dropdown-contents` and no styles', () => {
|
||||||
expect(wrapper.attributes('class')).toContain('labels-select-dropdown-contents');
|
expect(wrapper.attributes('class')).toContain('labels-select-dropdown-contents');
|
||||||
expect(wrapper.attributes('style')).toBe(undefined);
|
expect(wrapper.attributes('style')).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders component container element with styles when `renderOnTop` is true', () => {
|
describe('when `renderOnTop` is true', () => {
|
||||||
wrapper = createComponent(mockConfig, { renderOnTop: true });
|
it.each`
|
||||||
|
variant | expected
|
||||||
|
${DropdownVariant.Sidebar} | ${'bottom: 3rem'}
|
||||||
|
${DropdownVariant.Standalone} | ${'bottom: 2rem'}
|
||||||
|
${DropdownVariant.Embedded} | ${'bottom: 2rem'}
|
||||||
|
`('renders upward for $variant variant', ({ variant, expected }) => {
|
||||||
|
wrapper = createComponent({ ...mockConfig, variant }, { renderOnTop: true });
|
||||||
|
|
||||||
expect(wrapper.attributes('style')).toContain('bottom: 100%');
|
expect(wrapper.attributes('style')).toContain(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Vuex from 'vuex';
|
||||||
|
|
||||||
import { isInViewport } from '~/lib/utils/common_utils';
|
import { isInViewport } from '~/lib/utils/common_utils';
|
||||||
import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
|
import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
|
||||||
|
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
|
||||||
import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue';
|
import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue';
|
||||||
import DropdownContents from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue';
|
import DropdownContents from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue';
|
||||||
import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue';
|
import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue';
|
||||||
|
@ -190,40 +191,33 @@ describe('LabelsSelectRoot', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sets content direction based on viewport', () => {
|
describe('sets content direction based on viewport', () => {
|
||||||
it('does not set direction when `state.variant` is not "embedded"', async () => {
|
describe.each(Object.values(DropdownVariant))(
|
||||||
createComponent();
|
'when labels variant is "%s"',
|
||||||
|
({ variant }) => {
|
||||||
wrapper.vm.$store.dispatch('toggleDropdownContents');
|
beforeEach(() => {
|
||||||
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
|
createComponent({ ...mockConfig, variant });
|
||||||
await wrapper.vm.$nextTick;
|
wrapper.vm.$store.dispatch('toggleDropdownContents');
|
||||||
|
|
||||||
expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when `state.variant` is "embedded"', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
createComponent({ ...mockConfig, variant: 'embedded' });
|
|
||||||
wrapper.vm.$store.dispatch('toggleDropdownContents');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('set direction when out of viewport', () => {
|
|
||||||
isInViewport.mockImplementation(() => false);
|
|
||||||
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
|
|
||||||
|
|
||||||
return wrapper.vm.$nextTick().then(() => {
|
|
||||||
expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(true);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('does not set direction when inside of viewport', () => {
|
it('set direction when out of viewport', () => {
|
||||||
isInViewport.mockImplementation(() => true);
|
isInViewport.mockImplementation(() => false);
|
||||||
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
|
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
|
||||||
|
|
||||||
return wrapper.vm.$nextTick().then(() => {
|
return wrapper.vm.$nextTick().then(() => {
|
||||||
expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
|
expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
it('does not set direction when inside of viewport', () => {
|
||||||
|
isInViewport.mockImplementation(() => true);
|
||||||
|
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
|
||||||
|
|
||||||
|
return wrapper.vm.$nextTick().then(() => {
|
||||||
|
expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -282,7 +282,14 @@ RSpec.describe Issues::UpdateService, :mailer do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'filters out params that cannot be set without the :admin_issue permission' do
|
it 'filters out params that cannot be set without the :admin_issue permission' do
|
||||||
described_class.new(project, guest, opts.merge(confidential: true)).execute(issue)
|
described_class.new(
|
||||||
|
project,
|
||||||
|
guest,
|
||||||
|
opts.merge(
|
||||||
|
confidential: true,
|
||||||
|
issue_type: 'test_case'
|
||||||
|
)
|
||||||
|
).execute(issue)
|
||||||
|
|
||||||
expect(issue).to be_valid
|
expect(issue).to be_valid
|
||||||
expect(issue.title).to eq 'New title'
|
expect(issue.title).to eq 'New title'
|
||||||
|
@ -293,6 +300,7 @@ RSpec.describe Issues::UpdateService, :mailer do
|
||||||
expect(issue.due_date).to be_nil
|
expect(issue.due_date).to be_nil
|
||||||
expect(issue.discussion_locked).to be_falsey
|
expect(issue.discussion_locked).to be_falsey
|
||||||
expect(issue.confidential).to be_falsey
|
expect(issue.confidential).to be_falsey
|
||||||
|
expect(issue.issue_type).to eql('issue')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe 'shared/runners/show.html.haml' do
|
RSpec.describe 'shared/runners/_runner_details.html.haml' do
|
||||||
include PageLayoutHelper
|
include PageLayoutHelper
|
||||||
|
|
||||||
let(:runner) do
|
let(:runner) do
|
||||||
|
@ -14,7 +14,7 @@ RSpec.describe 'shared/runners/show.html.haml' do
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
assign(:runner, runner)
|
allow(view).to receive(:runner) { runner }
|
||||||
end
|
end
|
||||||
|
|
||||||
subject do
|
subject do
|
||||||
|
@ -24,7 +24,7 @@ RSpec.describe 'shared/runners/show.html.haml' do
|
||||||
|
|
||||||
describe 'Page title' do
|
describe 'Page title' do
|
||||||
before do
|
before do
|
||||||
expect_any_instance_of(PageLayoutHelper).to receive(:page_title).with("#{runner.description} ##{runner.id}", 'Runners')
|
expect(view).to receive(:page_title).with("##{runner.id} (#{runner.short_sha})")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets proper page title' do
|
it 'sets proper page title' do
|
||||||
|
@ -147,7 +147,7 @@ RSpec.describe 'shared/runners/show.html.haml' do
|
||||||
|
|
||||||
context 'when runner have already contacted' do
|
context 'when runner have already contacted' do
|
||||||
let(:runner) { create(:ci_runner, contacted_at: DateTime.now - 6.days) }
|
let(:runner) { create(:ci_runner, contacted_at: DateTime.now - 6.days) }
|
||||||
let(:expected_contacted_at) { I18n.localize(runner.contacted_at, format: "%b %d, %Y") }
|
let(:expected_contacted_at) { I18n.l(runner.contacted_at, format: "%b %d, %Y") }
|
||||||
|
|
||||||
it { is_expected.to have_content("Last contact #{expected_contacted_at}") }
|
it { is_expected.to have_content("Last contact #{expected_contacted_at}") }
|
||||||
end
|
end
|
Loading…
Reference in a new issue