diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-10 21:08:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-10 21:08:54 +0300 |
commit | c596046be917b250019fdfb509be85cfb48df152 (patch) | |
tree | 2c3716f6f70b42e0683022269a13117704d85d15 | |
parent | 9afe9ca576408a1145b6250d9493032fe65255ae (diff) |
Add latest changes from gitlab-org/gitlab@master
137 files changed, 2071 insertions, 386 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index aedcd4f71a0..b478539846c 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -584,6 +584,8 @@ rules: - <<: *if-not-ee when: never + - <<: *if-merge-request + changes: *code-backstage-patterns - <<: *if-master-schedule-2-hourly - <<: *if-merge-request-title-run-all-rspec @@ -45,6 +45,7 @@ gem 'omniauth_crowd', '~> 2.4.0' gem 'omniauth-authentiq', '~> 0.3.3' gem 'omniauth_openid_connect', '~> 0.3.5' gem 'omniauth-salesforce', '~> 1.0.5' +gem 'omniauth-atlassian-oauth2', '~> 0.2.0' gem 'rack-oauth2', '~> 1.9.3' gem 'jwt', '~> 2.1.0' diff --git a/Gemfile.lock b/Gemfile.lock index 4b97e6cf24e..75e5cfd5508 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -737,6 +737,9 @@ GEM omniauth (1.9.0) hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) + omniauth-atlassian-oauth2 (0.2.0) + omniauth (>= 1.1.1) + omniauth-oauth2 (>= 1.5) omniauth-auth0 (2.0.0) omniauth-oauth2 (~> 1.4) omniauth-authentiq (0.3.3) @@ -1393,6 +1396,7 @@ DEPENDENCIES octokit (~> 4.15) oj (~> 3.10.6) omniauth (~> 1.8) + omniauth-atlassian-oauth2 (~> 0.2.0) omniauth-auth0 (~> 2.0.0) omniauth-authentiq (~> 0.3.3) omniauth-azure-oauth2 (~> 0.0.9) diff --git a/app/assets/images/auth_buttons/atlassian_64.png b/app/assets/images/auth_buttons/atlassian_64.png Binary files differnew file mode 100644 index 00000000000..548f1c93318 --- /dev/null +++ b/app/assets/images/auth_buttons/atlassian_64.png diff --git a/app/assets/javascripts/admin/dev_ops_score/components/usage_ping_disabled.vue b/app/assets/javascripts/admin/dev_ops_report/components/usage_ping_disabled.vue index 5429ec403d3..5429ec403d3 100644 --- a/app/assets/javascripts/admin/dev_ops_score/components/usage_ping_disabled.vue +++ b/app/assets/javascripts/admin/dev_ops_report/components/usage_ping_disabled.vue diff --git a/app/assets/javascripts/issue_show/components/incidents/highlight_bar/graphql/queries/get_highlight_bar_info.graphql b/app/assets/javascripts/issue_show/components/incidents/highlight_bar/graphql/queries/get_highlight_bar_info.graphql new file mode 100644 index 00000000000..fe299adf53e --- /dev/null +++ b/app/assets/javascripts/issue_show/components/incidents/highlight_bar/graphql/queries/get_highlight_bar_info.graphql @@ -0,0 +1,12 @@ +query getHighlightBarInfo($iid: String!, $fullPath: ID!) { + project(fullPath: $fullPath) { + issue(iid: $iid) { + alertManagementAlert { + title + detailsUrl + createdAt + eventCount + } + } + } +} diff --git a/app/assets/javascripts/issue_show/components/incidents/highlight_bar/higlight_bar.vue b/app/assets/javascripts/issue_show/components/incidents/highlight_bar/higlight_bar.vue new file mode 100644 index 00000000000..dbef301856d --- /dev/null +++ b/app/assets/javascripts/issue_show/components/incidents/highlight_bar/higlight_bar.vue @@ -0,0 +1,51 @@ +<script> +import { GlLink } from '@gitlab/ui'; +import { formatDate } from '~/lib/utils/datetime_utility'; +import getHighlightBarInfo from './graphql/queries/get_highlight_bar_info.graphql'; + +export default { + components: { + GlLink, + }, + inject: ['fullPath', 'iid'], + apollo: { + alert: { + query: getHighlightBarInfo, + variables() { + return { + fullPath: this.fullPath, + iid: this.iid, + }; + }, + update: data => data.project?.issue?.alertManagementAlert, + }, + }, + computed: { + startTime() { + return formatDate(this.alert.createdAt, 'yyyy-mm-dd Z'); + }, + }, +}; +</script> + +<template> + <div + v-if="alert" + class="gl-border-solid gl-border-1 gl-border-gray-100 gl-p-5 gl-mb-3 gl-rounded-base gl-display-flex gl-justify-content-space-between" + > + <div class="text-truncate gl-pr-3"> + <span class="gl-font-weight-bold">{{ s__('HighlightBar|Original alert:') }}</span> + <gl-link :href="alert.detailsUrl">{{ alert.title }}</gl-link> + </div> + + <div class="gl-pr-3 gl-white-space-nowrap"> + <span class="gl-font-weight-bold">{{ s__('HighlightBar|Alert start time:') }}</span> + {{ startTime }} + </div> + + <div class="gl-white-space-nowrap"> + <span class="gl-font-weight-bold">{{ s__('HighlightBar|Alert events:') }}</span> + <span>{{ alert.eventCount }}</span> + </div> + </div> +</template> diff --git a/app/assets/javascripts/issue_show/components/incident_tabs.vue b/app/assets/javascripts/issue_show/components/incidents/incident_tabs.vue index f6e82cfaa74..e5dde1aaca6 100644 --- a/app/assets/javascripts/issue_show/components/incident_tabs.vue +++ b/app/assets/javascripts/issue_show/components/incidents/incident_tabs.vue @@ -1,24 +1,23 @@ <script> import { GlTab, GlTabs } from '@gitlab/ui'; -import DescriptionComponent from './description.vue'; +import DescriptionComponent from '../description.vue'; +import HighlightBar from './highlight_bar/higlight_bar.vue'; export default { components: { GlTab, GlTabs, DescriptionComponent, + HighlightBar, }, }; </script> <template> <div> - <gl-tabs - content-class="gl-reset-line-height gl-mt-3" - class="gl-mt-n3" - data-testid="incident-tabs" - > + <gl-tabs content-class="gl-reset-line-height" class="gl-mt-n3" data-testid="incident-tabs"> <gl-tab :title="__('Summary')"> + <highlight-bar /> <description-component v-bind="$attrs" /> </gl-tab> </gl-tabs> diff --git a/app/assets/javascripts/issue_show/incident.js b/app/assets/javascripts/issue_show/incident.js index 82b862a2195..a34e75ee64a 100644 --- a/app/assets/javascripts/issue_show/incident.js +++ b/app/assets/javascripts/issue_show/incident.js @@ -1,13 +1,28 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; import issuableApp from './components/app.vue'; -import incidentTabs from './components/incident_tabs.vue'; +import incidentTabs from './components/incidents/incident_tabs.vue'; + +Vue.use(VueApollo); export default function initIssuableApp(issuableData = {}) { + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), + }); + + const { projectNamespace, projectPath, iid } = issuableData; + return new Vue({ el: document.getElementById('js-issuable-app'), + apolloProvider, components: { issuableApp, }, + provide: { + fullPath: `${projectNamespace}/${projectPath}`, + iid, + }, render(createElement) { return createElement('issuable-app', { props: { diff --git a/app/assets/javascripts/pages/admin/dev_ops_score/index.js b/app/assets/javascripts/pages/admin/dev_ops_report/index.js index 9d018408b88..643497003ba 100644 --- a/app/assets/javascripts/pages/admin/dev_ops_score/index.js +++ b/app/assets/javascripts/pages/admin/dev_ops_report/index.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import UserCallout from '~/user_callout'; -import UsagePingDisabled from '~/admin/dev_ops_score/components/usage_ping_disabled.vue'; +import UsagePingDisabled from '~/admin/dev_ops_report/components/usage_ping_disabled.vue'; document.addEventListener('DOMContentLoaded', () => { // eslint-disable-next-line no-new diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue index 589636c620b..aa53c5040e8 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue @@ -2,13 +2,11 @@ import { mapGetters } from 'vuex'; import { GlTooltipDirective, GlFriendlyWrap, GlIcon } from '@gitlab/ui'; import { __ } from '~/locale'; -import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; export default { name: 'TestsSuiteTable', components: { GlIcon, - SmartVirtualList, GlFriendlyWrap, }, directives: { @@ -27,8 +25,6 @@ export default { return this.getSuiteTests.length > 0; }, }, - maxShownRows: 30, - typicalRowHeight: 75, wrapSymbols: ['::', '#', '.', '_', '-', '/', '\\'], }; </script> @@ -60,66 +56,60 @@ export default { </div> </div> - <smart-virtual-list - :length="getSuiteTests.length" - :remain="$options.maxShownRows" - :size="$options.typicalRowHeight" + <div + v-for="(testCase, index) in getSuiteTests" + :key="index" + class="gl-responsive-table-row rounded align-items-md-start mt-xs-3 js-case-row" > - <div - v-for="(testCase, index) in getSuiteTests" - :key="index" - class="gl-responsive-table-row rounded align-items-md-start mt-xs-3 js-case-row" - > - <div class="table-section section-20 section-wrap"> - <div role="rowheader" class="table-mobile-header">{{ __('Suite') }}</div> - <div class="table-mobile-content pr-md-1 gl-overflow-wrap-break"> - <gl-friendly-wrap :symbols="$options.wrapSymbols" :text="testCase.classname" /> - </div> + <div class="table-section section-20 section-wrap"> + <div role="rowheader" class="table-mobile-header">{{ __('Suite') }}</div> + <div class="table-mobile-content pr-md-1 gl-overflow-wrap-break"> + <gl-friendly-wrap :symbols="$options.wrapSymbols" :text="testCase.classname" /> </div> + </div> - <div class="table-section section-20 section-wrap"> - <div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div> - <div class="table-mobile-content pr-md-1 gl-overflow-wrap-break"> - <gl-friendly-wrap - data-testid="caseName" - :symbols="$options.wrapSymbols" - :text="testCase.name" - /> - </div> + <div class="table-section section-20 section-wrap"> + <div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div> + <div class="table-mobile-content pr-md-1 gl-overflow-wrap-break"> + <gl-friendly-wrap + data-testid="caseName" + :symbols="$options.wrapSymbols" + :text="testCase.name" + /> </div> + </div> - <div class="table-section section-10 section-wrap"> - <div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div> - <div class="table-mobile-content text-center"> - <div - class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center" - :class="`ci-status-icon-${testCase.status}`" - > - <gl-icon :size="24" :name="testCase.icon" /> - </div> + <div class="table-section section-10 section-wrap"> + <div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div> + <div class="table-mobile-content text-center"> + <div + class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center" + :class="`ci-status-icon-${testCase.status}`" + > + <gl-icon :size="24" :name="testCase.icon" /> </div> </div> + </div> - <div class="table-section flex-grow-1"> - <div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div> - <div class="table-mobile-content"> - <pre - v-if="testCase.system_output" - class="build-trace build-trace-rounded text-left" - ><code class="bash p-0">{{testCase.system_output}}</code></pre> - </div> + <div class="table-section flex-grow-1"> + <div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div> + <div class="table-mobile-content"> + <pre + v-if="testCase.system_output" + class="build-trace build-trace-rounded text-left" + ><code class="bash p-0">{{testCase.system_output}}</code></pre> </div> + </div> - <div class="table-section section-10 section-wrap"> - <div role="rowheader" class="table-mobile-header"> - {{ __('Duration') }} - </div> - <div class="table-mobile-content text-right pr-sm-1"> - {{ testCase.formattedTime }} - </div> + <div class="table-section section-10 section-wrap"> + <div role="rowheader" class="table-mobile-header"> + {{ __('Duration') }} + </div> + <div class="table-mobile-content text-right pr-sm-1"> + {{ testCase.formattedTime }} </div> </div> - </smart-virtual-list> + </div> </div> <div v-else> diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue index e774fe06fbe..5f9c0be3ccc 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue @@ -2,13 +2,11 @@ import { mapGetters } from 'vuex'; import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { s__ } from '~/locale'; -import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; export default { name: 'TestsSummaryTable', components: { GlIcon, - SmartVirtualList, }, directives: { GlTooltip: GlTooltipDirective, @@ -31,8 +29,6 @@ export default { this.$emit('row-click', index); }, }, - maxShownRows: 20, - typicalRowHeight: 55, }; </script> @@ -69,83 +65,77 @@ export default { </div> </div> - <smart-virtual-list - :length="getTestSuites.length" - :remain="$options.maxShownRows" - :size="$options.typicalRowHeight" + <div + v-for="(testSuite, index) in getTestSuites" + :key="index" + role="row" + class="gl-responsive-table-row test-reports-summary-row rounded js-suite-row" + :class="{ + 'gl-responsive-table-row-clickable cursor-pointer': !testSuite.suite_error, + }" + @click="tableRowClick(index)" > - <div - v-for="(testSuite, index) in getTestSuites" - :key="index" - role="row" - class="gl-responsive-table-row test-reports-summary-row rounded js-suite-row" - :class="{ - 'gl-responsive-table-row-clickable cursor-pointer': !testSuite.suite_error, - }" - @click="tableRowClick(index)" - > - <div class="table-section section-25"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Suite') }} - </div> - <div class="table-mobile-content underline cgray pl-3"> - {{ testSuite.name }} - <gl-icon - v-if="testSuite.suite_error" - ref="suiteErrorIcon" - v-gl-tooltip - name="error" - :title="testSuite.suite_error" - class="vertical-align-middle" - /> - </div> + <div class="table-section section-25"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Suite') }} </div> + <div class="table-mobile-content underline cgray pl-3"> + {{ testSuite.name }} + <gl-icon + v-if="testSuite.suite_error" + ref="suiteErrorIcon" + v-gl-tooltip + name="error" + :title="testSuite.suite_error" + class="vertical-align-middle" + /> + </div> + </div> - <div class="table-section section-25"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Duration') }} - </div> - <div class="table-mobile-content text-md-left"> - {{ testSuite.formattedTime }} - </div> + <div class="table-section section-25"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Duration') }} + </div> + <div class="table-mobile-content text-md-left"> + {{ testSuite.formattedTime }} </div> + </div> - <div class="table-section section-10 text-center"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Failed') }} - </div> - <div class="table-mobile-content">{{ testSuite.failed_count }}</div> + <div class="table-section section-10 text-center"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Failed') }} </div> + <div class="table-mobile-content">{{ testSuite.failed_count }}</div> + </div> - <div class="table-section section-10 text-center"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Errors') }} - </div> - <div class="table-mobile-content">{{ testSuite.error_count }}</div> + <div class="table-section section-10 text-center"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Errors') }} </div> + <div class="table-mobile-content">{{ testSuite.error_count }}</div> + </div> - <div class="table-section section-10 text-center"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Skipped') }} - </div> - <div class="table-mobile-content">{{ testSuite.skipped_count }}</div> + <div class="table-section section-10 text-center"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Skipped') }} </div> + <div class="table-mobile-content">{{ testSuite.skipped_count }}</div> + </div> - <div class="table-section section-10 text-center"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Passed') }} - </div> - <div class="table-mobile-content">{{ testSuite.success_count }}</div> + <div class="table-section section-10 text-center"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Passed') }} </div> + <div class="table-mobile-content">{{ testSuite.success_count }}</div> + </div> - <div class="table-section section-10 text-right pr-md-3"> - <div role="rowheader" class="table-mobile-header font-weight-bold"> - {{ __('Total') }} - </div> - <div class="table-mobile-content">{{ testSuite.total_count }}</div> + <div class="table-section section-10 text-right pr-md-3"> + <div role="rowheader" class="table-mobile-header font-weight-bold"> + {{ __('Total') }} </div> + <div class="table-mobile-content">{{ testSuite.total_count }}</div> </div> - </smart-virtual-list> + </div> </div> <div v-else> diff --git a/app/assets/javascripts/sidebar/components/severity/constants.js b/app/assets/javascripts/sidebar/components/severity/constants.js new file mode 100644 index 00000000000..c7d6a51ea1c --- /dev/null +++ b/app/assets/javascripts/sidebar/components/severity/constants.js @@ -0,0 +1,43 @@ +import { __, s__ } from '~/locale'; + +export const INCIDENT_SEVERITY = { + CRITICAL: { + value: 'CRITICAL', + icon: 'critical', + label: s__('IncidentManagement|Critical - S1'), + }, + HIGH: { + value: 'HIGH', + icon: 'high', + label: s__('IncidentManagement|High - S2'), + }, + MEDIUM: { + value: 'MEDIUM', + icon: 'medium', + label: s__('IncidentManagement|Medium - S3'), + }, + LOW: { + value: 'LOW', + icon: 'low', + label: s__('IncidentManagement|Low - S4'), + }, + UNKNOWN: { + value: 'UNKNOWN', + icon: 'unknown', + label: s__('IncidentManagement|Unknown'), + }, +}; + +export const ISSUABLE_TYPES = { + INCIDENT: 'incident', +}; + +export const SIDEBAR_ANIMATION_DURATION = 300; + +export const I18N = { + UPDATE_SEVERITY_ERROR: s__('SeverityWidget|There was an error while updating severity.'), + TRY_AGAIN: __('Please try again'), + EDIT: __('Edit'), + SEVERITY: s__('SeverityWidget|Severity'), + SEVERITY_VALUE: s__('SeverityWidget|Severity: %{severity}'), +}; diff --git a/app/assets/javascripts/sidebar/components/severity/graphql/mutations/update_issuable_severity.mutation.graphql b/app/assets/javascripts/sidebar/components/severity/graphql/mutations/update_issuable_severity.mutation.graphql new file mode 100644 index 00000000000..750e757971f --- /dev/null +++ b/app/assets/javascripts/sidebar/components/severity/graphql/mutations/update_issuable_severity.mutation.graphql @@ -0,0 +1,9 @@ +mutation updateIssuableSeverity($projectPath: ID!, $severity: IssuableSeverity!, $iid: String!) { + issueSetSeverity(input: { iid: $iid, severity: $severity, projectPath: $projectPath }) { + errors + issue { + iid + severity + } + } +} diff --git a/app/assets/javascripts/sidebar/components/severity/severity.vue b/app/assets/javascripts/sidebar/components/severity/severity.vue new file mode 100644 index 00000000000..7e7d62256c9 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/severity/severity.vue @@ -0,0 +1,42 @@ +<script> +import { GlIcon } from '@gitlab/ui'; + +export default { + components: { + GlIcon, + }, + props: { + severity: { + type: Object, + required: true, + validator(severity) { + const { value, label, icon } = severity; + return value && label && icon; + }, + }, + iconSize: { + type: Number, + required: false, + default: 12, + }, + iconOnly: { + type: Boolean, + required: false, + default: false, + }, + }, +}; +</script> + +<template> + <div + class="incident-severity gl-display-inline-flex gl-align-items-center gl-justify-content-between" + > + <gl-icon + :size="iconSize" + :name="`severity-${severity.icon}`" + :class="[`icon-${severity.icon}`, { 'gl-mr-3': !iconOnly }]" + /> + <span v-if="!iconOnly">{{ severity.label }}</span> + </div> +</template> diff --git a/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue b/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue new file mode 100644 index 00000000000..65ff7a6d2ac --- /dev/null +++ b/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue @@ -0,0 +1,195 @@ +<script> +import { + GlDropdown, + GlDropdownItem, + GlLoadingIcon, + GlTooltip, + GlSprintf, + GlLink, +} from '@gitlab/ui'; +import { INCIDENT_SEVERITY, ISSUABLE_TYPES, SIDEBAR_ANIMATION_DURATION, I18N } from './constants'; +import updateIssuableSeverity from './graphql/mutations/update_issuable_severity.mutation.graphql'; +import SeverityToken from './severity.vue'; +import createFlash from '~/flash'; + +export default { + i18n: I18N, + components: { + GlLoadingIcon, + GlTooltip, + GlSprintf, + GlDropdown, + GlDropdownItem, + GlLink, + SeverityToken, + }, + props: { + projectPath: { + type: String, + required: true, + }, + iid: { + type: String, + required: true, + }, + initialSeverity: { + type: String, + required: false, + default: INCIDENT_SEVERITY.UNKNOWN.value, + }, + issuableType: { + type: String, + required: false, + default: ISSUABLE_TYPES.INCIDENT, + validator: value => { + // currently severity is supported only for incidents, but this list might be extended + return [ISSUABLE_TYPES.INCIDENT].includes(value); + }, + }, + }, + data() { + return { + isDropdownShowing: false, + isUpdating: false, + severity: this.initialSeverity, + }; + }, + computed: { + severitiesList() { + switch (this.issuableType) { + case ISSUABLE_TYPES.INCIDENT: + return Object.values(INCIDENT_SEVERITY); + default: + return []; + } + }, + dropdownClass() { + return this.isDropdownShowing ? 'show' : 'gl-display-none'; + }, + selectedItem() { + return this.severitiesList.find(severity => severity.value === this.severity); + }, + }, + mounted() { + document.addEventListener('click', this.handleOffClick); + }, + beforeDestroy() { + document.removeEventListener('click', this.handleOffClick); + }, + methods: { + handleOffClick(event) { + if (!this.isDropdownShowing) { + return; + } + + if (!this.$refs.sidebarSeverity.contains(event.target)) { + this.hideDropdown(); + } + }, + hideDropdown() { + this.isDropdownShowing = false; + const event = new Event('hidden.gl.dropdown'); + this.$el.dispatchEvent(event); + }, + toggleFormDropdown(collapsedSidebar) { + this.isDropdownShowing = !this.isDropdownShowing; + const timeout = collapsedSidebar ? SIDEBAR_ANIMATION_DURATION : 0; + setTimeout(() => { + const { dropdown } = this.$refs; + if (dropdown && this.isDropdownShowing) { + dropdown.show(); + } + }, timeout); + }, + updateSeverity(value) { + this.hideDropdown(); + this.isUpdating = true; + this.$apollo + .mutate({ + mutation: updateIssuableSeverity, + variables: { + iid: this.iid, + severity: value, + projectPath: this.projectPath, + }, + }) + .then(resp => { + const { + data: { + issueSetSeverity: { + errors = [], + issue: { severity }, + }, + }, + } = resp; + + if (errors[0]) { + throw errors[0]; + } + this.severity = severity; + }) + .catch(() => + createFlash({ + message: `${this.$options.i18n.UPDATE_SEVERITY_ERROR} ${this.$options.i18n.TRY_AGAIN}`, + }), + ) + .finally(() => { + this.isUpdating = false; + }); + }, + }, +}; +</script> + +<template> + <div ref="sidebarSeverity" class="block"> + <div ref="severity" class="sidebar-collapsed-icon" @click="toggleFormDropdown(true)"> + <severity-token :severity="selectedItem" :icon-size="14" :icon-only="true" /> + <gl-tooltip :target="() => $refs.severity" boundary="viewport" placement="left"> + <gl-sprintf :message="$options.i18n.SEVERITY_VALUE"> + <template #severity> + {{ selectedItem.label }} + </template> + </gl-sprintf> + </gl-tooltip> + </div> + + <div class="hide-collapsed"> + <p class="title gl-display-flex gl-justify-content-space-between"> + {{ $options.i18n.SEVERITY }} + <gl-link + data-testid="editButton" + href="#" + @click="toggleFormDropdown" + @keydown.esc="hideDropdown" + > + {{ $options.i18n.EDIT }} + </gl-link> + </p> + + <gl-dropdown + ref="dropdown" + :class="dropdownClass" + block + :text="selectedItem.label" + toggle-class="dropdown-menu-toggle gl-mb-2" + @keydown.esc.native="hideDropdown" + > + <gl-dropdown-item + v-for="option in severitiesList" + :key="option.value" + data-testid="severityDropdownItem" + :is-check-item="true" + :is-checked="option.value === severity" + @click="updateSeverity(option.value)" + > + <severity-token :severity="option" /> + </gl-dropdown-item> + </gl-dropdown> + + <gl-loading-icon v-if="isUpdating" :inline="true" /> + + <severity-token v-else-if="!isDropdownShowing" :severity="selectedItem" /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js index 015219200db..e7520cb096a 100644 --- a/app/assets/javascripts/sidebar/mount_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_sidebar.js @@ -8,6 +8,7 @@ import SidebarMoveIssue from './lib/sidebar_move_issue'; import IssuableLockForm from './components/lock/issuable_lock_form.vue'; import sidebarParticipants from './components/participants/sidebar_participants.vue'; import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptions.vue'; +import SidebarSeverity from './components/severity/sidebar_severity.vue'; import Translate from '../vue_shared/translate'; import createDefaultClient from '~/lib/graphql'; import { store } from '~/notes/stores'; @@ -159,6 +160,35 @@ function mountTimeTrackingComponent() { }); } +function mountSeverityComponent() { + const severityContainerEl = document.querySelector('#js-severity'); + + if (!severityContainerEl) { + return false; + } + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), + }); + + const { fullPath, iid, severity } = getSidebarOptions(); + + return new Vue({ + el: severityContainerEl, + apolloProvider, + components: { + SidebarSeverity, + }, + render: createElement => + createElement('sidebar-severity', { + props: { + projectPath: fullPath, + iid: String(iid), + initialSeverity: severity.toUpperCase(), + }, + }), + }); +} + export function mountSidebar(mediator) { mountAssigneesComponent(mediator); mountConfidentialComponent(mediator); @@ -173,6 +203,8 @@ export function mountSidebar(mediator) { ).init(); mountTimeTrackingComponent(); + + mountSeverityComponent(); } export { getSidebarOptions }; diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss index b27602f6b4a..f706b615e7e 100644 --- a/app/assets/stylesheets/_page_specific_files.scss +++ b/app/assets/stylesheets/_page_specific_files.scss @@ -10,7 +10,7 @@ @import './pages/cycle_analytics'; @import './pages/deploy_keys'; @import './pages/detail_page'; -@import './pages/dev_ops_score'; +@import './pages/dev_ops_report'; @import './pages/diff'; @import './pages/editor'; @import './pages/environment_logs'; diff --git a/app/assets/stylesheets/pages/alert_management/severity-icons.scss b/app/assets/stylesheets/pages/alert_management/severity-icons.scss index 6004697b3e1..f58ad87a673 100644 --- a/app/assets/stylesheets/pages/alert_management/severity-icons.scss +++ b/app/assets/stylesheets/pages/alert_management/severity-icons.scss @@ -1,3 +1,4 @@ +.incident-severity, .incident-management-list, .alert-management-details { .icon-critical { diff --git a/app/assets/stylesheets/pages/dev_ops_score.scss b/app/assets/stylesheets/pages/dev_ops_report.scss index a6bc9b9332b..a6bc9b9332b 100644 --- a/app/assets/stylesheets/pages/dev_ops_score.scss +++ b/app/assets/stylesheets/pages/dev_ops_report.scss diff --git a/app/controllers/admin/dev_ops_score_controller.rb b/app/controllers/admin/dev_ops_report_controller.rb index 3e012e7d76b..bed0d51c331 100644 --- a/app/controllers/admin/dev_ops_score_controller.rb +++ b/app/controllers/admin/dev_ops_report_controller.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -class Admin::DevOpsScoreController < Admin::ApplicationController +class Admin::DevOpsReportController < Admin::ApplicationController include Analytics::UniqueVisitsHelper track_unique_visits :show, target_id: 'i_analytics_dev_ops_score' # rubocop: disable CodeReuse/ActiveRecord def show - @metric = DevOpsScore::Metric.order(:created_at).last&.present + @metric = DevOpsReport::Metric.order(:created_at).last&.present end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 0245c00aacb..6414792dd43 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -19,7 +19,7 @@ class Admin::GroupsController < Admin::ApplicationController # the Group with statistics). @group = Group.with_statistics.find(group&.id) @members = present_members( - @group.members.order("access_level DESC").page(params[:members_page])) + group_members.order("access_level DESC").page(params[:members_page])) @requesters = present_members( AccessRequestsFinder.new(@group).execute(current_user)) @projects = @group.projects.with_statistics.page(params[:projects_page]) @@ -82,6 +82,10 @@ class Admin::GroupsController < Admin::ApplicationController @group ||= Group.find_by_full_path(params[:id]) end + def group_members + @group.members + end + def group_params params.require(:group).permit(allowed_group_params) end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index a558b01f0c6..b798d6680bc 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -83,6 +83,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController end end + def atlassian_oauth2 + omniauth_flow(Gitlab::Auth::Atlassian) + end + private def log_failed_login(user, provider) diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb index 95e055a44db..b19285e98bb 100644 --- a/app/controllers/profiles/accounts_controller.rb +++ b/app/controllers/profiles/accounts_controller.rb @@ -7,10 +7,9 @@ class Profiles::AccountsController < Profiles::ApplicationController render(locals: show_view_variables) end - # rubocop: disable CodeReuse/ActiveRecord def unlink provider = params[:provider] - identity = current_user.identities.find_by(provider: provider) + identity = find_identity(provider) return render_404 unless identity @@ -22,13 +21,18 @@ class Profiles::AccountsController < Profiles::ApplicationController redirect_to profile_account_path end - # rubocop: enable CodeReuse/ActiveRecord private def show_view_variables {} end + + def find_identity(provider) + return current_user.atlassian_identity if provider == 'atlassian_oauth2' + + current_user.identities.find_by(provider: provider) # rubocop: disable CodeReuse/ActiveRecord + end end Profiles::AccountsController.prepend_if_ee('EE::Profiles::AccountsController') diff --git a/app/controllers/projects/static_site_editor_controller.rb b/app/controllers/projects/static_site_editor_controller.rb index 9ec50ff8196..3251814bac8 100644 --- a/app/controllers/projects/static_site_editor_controller.rb +++ b/app/controllers/projects/static_site_editor_controller.rb @@ -14,7 +14,13 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController end def show - @config = Gitlab::StaticSiteEditor::Config.new(@repository, @ref, @path, params[:return_url]) + config = Gitlab::StaticSiteEditor::Config::CombinedConfig.new( + @repository, + @ref, + @path, + params[:return_url] + ) + @data = config.data end private diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb index a4b00588368..ce0d52ad97a 100644 --- a/app/finders/group_members_finder.rb +++ b/app/finders/group_members_finder.rb @@ -27,7 +27,7 @@ class GroupMembersFinder < UnionFinder relations << group_members if include_relations.include?(:direct) if include_relations.include?(:inherited) && group.parent - parents_members = GroupMember.non_request + parents_members = GroupMember.non_request.non_minimal_access .where(source_id: group.ancestors.select(:id)) .where.not(user_id: group.users.select(:id)) @@ -35,7 +35,7 @@ class GroupMembersFinder < UnionFinder end if include_relations.include?(:descendants) - descendant_members = GroupMember.non_request + descendant_members = GroupMember.non_request.non_minimal_access .where(source_id: group.descendants.select(:id)) .where.not(user_id: group.users.select(:id)) diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb index ce9137f91bb..013ed03a789 100644 --- a/app/finders/members_finder.rb +++ b/app/finders/members_finder.rb @@ -63,7 +63,7 @@ class MembersFinder def direct_group_members(include_descendants) requested_relations = [:inherited, :direct] requested_relations << :descendants if include_descendants - GroupMembersFinder.new(group).execute(include_relations: requested_relations).non_invite # rubocop: disable CodeReuse/Finder + GroupMembersFinder.new(group).execute(include_relations: requested_relations).non_invite.non_minimal_access # rubocop: disable CodeReuse/Finder end def project_invited_groups_members @@ -73,7 +73,7 @@ class MembersFinder .public_or_visible_to_user(current_user) .select(:id) - GroupMember.with_source_id(invited_groups_ids_including_ancestors) + GroupMember.with_source_id(invited_groups_ids_including_ancestors).non_minimal_access end def distinct_union_of_members(union_members) diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index a57e27d23c8..7f8cb66a84f 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module AuthHelper - PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq salesforce).freeze + PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq salesforce atlassian_oauth2).freeze LDAP_PROVIDER = /\Aldap/.freeze def ldap_enabled? @@ -133,6 +133,8 @@ module AuthHelper # rubocop: disable CodeReuse/ActiveRecord def auth_active?(provider) + return current_user.atlassian_identity.present? if provider == :atlassian_oauth2 + current_user.identities.exists?(provider: provider.to_s) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/helpers/dev_ops_score_helper.rb b/app/helpers/dev_ops_report_helper.rb index 9a673998149..ab7e56fc1a2 100644 --- a/app/helpers/dev_ops_score_helper.rb +++ b/app/helpers/dev_ops_report_helper.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module DevOpsScoreHelper +module DevOpsReportHelper def score_level(score) if score < 33.33 'low' diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 398e76b6697..54fa3ca57b8 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -294,7 +294,8 @@ module IssuablesHelper hasClosingMergeRequest: issuable.merge_requests_count(current_user) != 0, issueType: issuable.issue_type, zoomMeetingUrl: ZoomMeeting.canonical_meeting_url(issuable), - sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier # rubocop:disable CodeReuse/ActiveRecord + sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier, # rubocop:disable CodeReuse/ActiveRecord + iid: issuable.iid.to_s } end @@ -465,6 +466,7 @@ module IssuablesHelper rootPath: root_path, fullPath: issuable[:project_full_path], iid: issuable[:iid], + severity: issuable[:severity], timeTrackingLimitToHours: Gitlab::CurrentSettings.time_tracking_limit_to_hours } end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index ae9612308e4..2318596c63a 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -63,7 +63,7 @@ module NavHelper end def admin_analytics_nav_links - %w(dev_ops_score cohorts) + %w(dev_ops_report cohorts) end def group_issues_sub_menu_items diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb index 4bbfa499919..0227ad1092d 100644 --- a/app/helpers/system_note_helper.rb +++ b/app/helpers/system_note_helper.rb @@ -7,7 +7,7 @@ module SystemNoteHelper 'description' => 'pencil-square', 'merge' => 'git-merge', 'merged' => 'git-merge', - 'opened' => 'issue-open', + 'opened' => 'issues', 'closed' => 'issue-close', 'time_tracking' => 'timer', 'assignee' => 'user', diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 09b1dbaba58..888e1b384a2 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -185,6 +185,10 @@ module Issuable is_a?(TimeTrackable) && !incident? end + def supports_severity? + incident? + end + def incident? is_a?(Issue) && super end diff --git a/app/models/concerns/loaded_in_group_list.rb b/app/models/concerns/loaded_in_group_list.rb index 79ff82d9f99..e624b9aa356 100644 --- a/app/models/concerns/loaded_in_group_list.rb +++ b/app/models/concerns/loaded_in_group_list.rb @@ -54,6 +54,7 @@ module LoadedInGroupList .where(members[:source_type].eq(Namespace.name)) .where(members[:source_id].eq(namespaces[:id])) .where(members[:requested_at].eq(nil)) + .where(members[:access_level].gt(Gitlab::Access::MINIMAL_ACCESS)) end end @@ -70,7 +71,7 @@ module LoadedInGroupList end def member_count - @member_count ||= try(:preloaded_member_count) || users.count + @member_count ||= try(:preloaded_member_count) || members.count end end diff --git a/app/models/dev_ops_score/card.rb b/app/models/dev_ops_report/card.rb index b1894cf4138..4060cb1e5b6 100644 --- a/app/models/dev_ops_score/card.rb +++ b/app/models/dev_ops_report/card.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module DevOpsScore +module DevOpsReport class Card attr_accessor :metric, :title, :description, :feature, :blog, :docs diff --git a/app/models/dev_ops_score/idea_to_production_step.rb b/app/models/dev_ops_report/idea_to_production_step.rb index d892793cf97..2503d5949e5 100644 --- a/app/models/dev_ops_score/idea_to_production_step.rb +++ b/app/models/dev_ops_report/idea_to_production_step.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module DevOpsScore +module DevOpsReport class IdeaToProductionStep attr_accessor :metric, :title, :features diff --git a/app/models/dev_ops_score/metric.rb b/app/models/dev_ops_report/metric.rb index a9133128ce9..14eff725433 100644 --- a/app/models/dev_ops_score/metric.rb +++ b/app/models/dev_ops_report/metric.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module DevOpsScore +module DevOpsReport class Metric < ApplicationRecord include Presentable diff --git a/app/models/group.rb b/app/models/group.rb index f8cbaa2495c..5c6e946b02e 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -20,8 +20,10 @@ class Group < Namespace UpdateSharedRunnersError = Class.new(StandardError) - has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent + has_many :all_group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent + has_many :group_members, -> { where(requested_at: nil).where.not(members: { access_level: Gitlab::Access::MINIMAL_ACCESS }) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent alias_method :members, :group_members + has_many :users, through: :group_members has_many :owners, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, @@ -395,6 +397,10 @@ class Group < Namespace ]) end + def users_count + members.count + end + # Returns all users that are members of projects # belonging to the current group or sub-groups def project_users_with_descendants @@ -630,6 +636,7 @@ class Group < Namespace .where(group_member_table[:requested_at].eq(nil)) .where(group_member_table[:source_id].eq(group_group_link_table[:shared_with_group_id])) .where(group_member_table[:source_type].eq('Namespace')) + .non_minimal_access end def smallest_value_arel(args, column_alias) diff --git a/app/models/member.rb b/app/models/member.rb index 55bb78b7f94..5a084a3a2e6 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -25,7 +25,6 @@ class Member < ApplicationRecord validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source", allow_nil: true } - validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true validate :higher_access_level_than_group, unless: :importing? validates :invite_email, presence: { @@ -60,6 +59,7 @@ class Member < ApplicationRecord left_join_users .where(user_ok) .where(requested_at: nil) + .non_minimal_access .reorder(nil) end @@ -68,6 +68,8 @@ class Member < ApplicationRecord left_join_users .where(users: { state: 'active' }) .non_request + .non_invite + .non_minimal_access .reorder(nil) end @@ -85,6 +87,7 @@ class Member < ApplicationRecord scope :developers, -> { active.where(access_level: DEVELOPER) } scope :maintainers, -> { active.where(access_level: MAINTAINER) } scope :non_guests, -> { where('members.access_level > ?', GUEST) } + scope :non_minimal_access, -> { where('members.access_level > ?', MINIMAL_ACCESS) } scope :owners, -> { active.where(access_level: OWNER) } scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) } scope :with_user, -> (user) { where(user: user) } diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 8c224dea88f..34958936c9f 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -13,6 +13,9 @@ class GroupMember < Member # Make sure group member points only to group as it source default_value_for :source_type, SOURCE_TYPE validates :source_type, format: { with: /\ANamespace\z/ } + validates :access_level, presence: true + validate :access_level_inclusion + default_scope { where(source_type: SOURCE_TYPE) } # rubocop:disable Cop/DefaultScope scope :of_groups, ->(groups) { where(source_id: groups.select(:id)) } @@ -45,6 +48,12 @@ class GroupMember < Member private + def access_level_inclusion + return if access_level.in?(Gitlab::Access.all_values) + + errors.add(:access_level, "is not included in the list") + end + def send_invite run_after_commit_or_now { notification_service.invite_group_member(self, @raw_invite_token) } diff --git a/app/models/user.rb b/app/models/user.rb index 9c4af91ad7e..9b8fe4881ad 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -120,7 +120,7 @@ class User < ApplicationRecord # Groups has_many :members - has_many :group_members, -> { where(requested_at: nil) }, source: 'GroupMember' + has_many :group_members, -> { where(requested_at: nil).where("access_level >= ?", Gitlab::Access::GUEST) }, source: 'GroupMember' has_many :groups, through: :group_members has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group diff --git a/app/presenters/dev_ops_score/metric_presenter.rb b/app/presenters/dev_ops_score/metric_presenter.rb index d22beefee54..e7363293435 100644 --- a/app/presenters/dev_ops_score/metric_presenter.rb +++ b/app/presenters/dev_ops_score/metric_presenter.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module DevOpsScore +module DevOpsReport class MetricPresenter < Gitlab::View::Presenter::Simple def cards [ diff --git a/app/serializers/issuable_sidebar_basic_entity.rb b/app/serializers/issuable_sidebar_basic_entity.rb index 11df60ade70..53c123c06fd 100644 --- a/app/serializers/issuable_sidebar_basic_entity.rb +++ b/app/serializers/issuable_sidebar_basic_entity.rb @@ -105,6 +105,7 @@ class IssuableSidebarBasicEntity < Grape::Entity expose :supports_time_tracking?, as: :supports_time_tracking expose :supports_milestone?, as: :supports_milestone + expose :supports_severity?, as: :supports_severity private diff --git a/app/serializers/issue_sidebar_basic_entity.rb b/app/serializers/issue_sidebar_basic_entity.rb index 165dc462cfe..e9e05718af9 100644 --- a/app/serializers/issue_sidebar_basic_entity.rb +++ b/app/serializers/issue_sidebar_basic_entity.rb @@ -3,6 +3,7 @@ class IssueSidebarBasicEntity < IssuableSidebarBasicEntity expose :due_date expose :confidential + expose :severity end IssueSidebarBasicEntity.prepend_if_ee('EE::IssueSidebarBasicEntity') diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb index 9191943caa7..2fbeaf4405c 100644 --- a/app/services/submit_usage_ping_service.rb +++ b/app/services/submit_usage_ping_service.rb @@ -51,11 +51,11 @@ class SubmitUsagePingService end def store_metrics(response) - metrics = response['conv_index'] || response['dev_ops_score'] + metrics = response['conv_index'] || response['dev_ops_score'] # leaving dev_ops_score here, as the response data comes from the gitlab-version-com return unless metrics.present? - DevOpsScore::Metric.create!( + DevOpsReport::Metric.create!( metrics.slice(*METRICS) ) end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index b6ac5db4d2d..4acfc96caf2 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -17,37 +17,34 @@ .well-segment.well-centered = link_to admin_projects_path do %h3.text-center - Projects: - = approximate_count_with_delimiters(@counts, Project) + = s_('AdminArea|Projects: %{number_of_projects}') % { number_of_projects: approximate_count_with_delimiters(@counts, Project) } %hr - = link_to('New project', new_project_path, class: "btn btn-success gl-w-full") + = link_to(s_('AdminArea|New project'), new_project_path, class: "btn btn-success gl-w-full") .col-sm-4 .info-well.dark-well .well-segment.well-centered = link_to admin_users_path do %h3.text-center - Users: - = approximate_count_with_delimiters(@counts, User) + = s_('AdminArea|Users: %{number_of_users}') % { number_of_users: approximate_count_with_delimiters(@counts, User) } %hr .btn-group.d-flex{ role: 'group' } - = link_to 'New user', new_admin_user_path, class: "btn btn-success gl-w-full" + = link_to s_('AdminArea|New user'), new_admin_user_path, class: "btn btn-success gl-w-full" = link_to s_('AdminArea|Users statistics'), admin_dashboard_stats_path, class: 'btn btn-primary gl-w-full' .col-sm-4 .info-well.dark-well .well-segment.well-centered = link_to admin_groups_path do %h3.text-center - Groups: - = approximate_count_with_delimiters(@counts, Group) + = s_('AdminArea|Groups: %{number_of_groups}') % { number_of_groups: approximate_count_with_delimiters(@counts, Group) } %hr - = link_to 'New group', new_admin_group_path, class: "btn btn-success gl-w-full" + = link_to s_('AdminArea|New group'), new_admin_group_path, class: "btn btn-success gl-w-full" .row .col-md-4 #js-admin-statistics-container .col-md-4 .info-well .well-segment.admin-well.admin-well-features - %h4 Features + %h4= s_('AdminArea|Features') = feature_entry(_('Sign up'), href: general_admin_application_settings_path(anchor: 'js-signup-settings'), enabled: allow_signup?) @@ -87,42 +84,41 @@ .info-well .well-segment.admin-well %h4 - Components + = s_('AdminArea|Components') - if Gitlab::CurrentSettings.version_check_enabled .float-right = version_status_badge %p - %a{ href: general_admin_application_settings_path } - GitLab + = link_to _('GitLab'), general_admin_application_settings_path %span.float-right = Gitlab::VERSION = "(#{Gitlab.revision})" %p - GitLab Shell + = _('GitLab Shell') %span.float-right = Gitlab::Shell.version %p - GitLab Workhorse + = _('GitLab Workhorse') %span.float-right = gitlab_workhorse_version %p - GitLab API + = _('GitLab API') %span.float-right = API::API::version - if Gitlab.config.pages.enabled %p - GitLab Pages + = _('GitLab Pages') %span.float-right = Gitlab::Pages::VERSION = render_if_exists 'admin/dashboard/geo' %p - Ruby + = _('Ruby') %span.float-right #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} %p - Rails + = _('Rails') %span.float-right #{Rails::VERSION::STRING} %p @@ -130,12 +126,12 @@ %span.float-right = Gitlab::Database.version %p - = link_to "Gitaly Servers", admin_gitaly_servers_path + = link_to _("Gitaly Servers"), admin_gitaly_servers_path .row .col-md-4 .info-well .well-segment.admin-well - %h4 Latest projects + %h4= s_('AdminArea|Latest projects') - @projects.each do |project| %p = link_to project.full_name, admin_project_path(project), class: 'str-truncated-60' @@ -144,7 +140,7 @@ .col-md-4 .info-well .well-segment.admin-well - %h4 Latest users + %h4= s_('AdminArea|Latest users') - @users.each do |user| %p = link_to [:admin, user], class: 'str-truncated-60' do @@ -154,7 +150,7 @@ .col-md-4 .info-well .well-segment.admin-well - %h4 Latest groups + %h4= s_('AdminArea|Latest groups') - @groups.each do |group| %p = link_to [:admin, group], class: 'str-truncated-60' do diff --git a/app/views/admin/dev_ops_score/_callout.html.haml b/app/views/admin/dev_ops_report/_callout.html.haml index 1b50c27834d..220456a4295 100644 --- a/app/views/admin/dev_ops_score/_callout.html.haml +++ b/app/views/admin/dev_ops_report/_callout.html.haml @@ -1,5 +1,5 @@ .gl-mt-3 -.user-callout{ data: { uid: 'dev_ops_score_intro_callout_dismissed' } } +.user-callout{ data: { uid: 'dev_ops_report_intro_callout_dismissed' } } .bordered-box.landing.content-block %button.btn.btn-default.close.js-close-callout{ type: 'button', 'aria-label' => _('Dismiss DevOps Report introduction') } @@ -10,4 +10,4 @@ %p = _('Your DevOps Report gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers.') .svg-container.devops - = custom_icon('dev_ops_score_overview') + = custom_icon('dev_ops_report_overview') diff --git a/app/views/admin/dev_ops_score/_card.html.haml b/app/views/admin/dev_ops_report/_card.html.haml index dd6e5c0f108..dd6e5c0f108 100644 --- a/app/views/admin/dev_ops_score/_card.html.haml +++ b/app/views/admin/dev_ops_report/_card.html.haml diff --git a/app/views/admin/dev_ops_score/_no_data.html.haml b/app/views/admin/dev_ops_report/_no_data.html.haml index 8ea3797b1cc..585aa878b0b 100644 --- a/app/views/admin/dev_ops_score/_no_data.html.haml +++ b/app/views/admin/dev_ops_report/_no_data.html.haml @@ -1,6 +1,6 @@ .container.devops-empty .col-sm-12.justify-content-center.text-center - = custom_icon('dev_ops_score_no_data') + = custom_icon('dev_ops_report_no_data') %h4= _('Data is still calculating...') %p = _('In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index.') diff --git a/app/views/admin/dev_ops_score/show.html.haml b/app/views/admin/dev_ops_report/show.html.haml index 3a7fb9d7c8c..57d80bc8c85 100644 --- a/app/views/admin/dev_ops_score/show.html.haml +++ b/app/views/admin/dev_ops_report/show.html.haml @@ -2,7 +2,7 @@ - usage_ping_enabled = Gitlab::CurrentSettings.usage_ping_enabled .container - - if usage_ping_enabled && show_callout?('dev_ops_score_intro_callout_dismissed') + - if usage_ping_enabled && show_callout?('dev_ops_report_intro_callout_dismissed') = render 'callout' .gl-mt-3 diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index ab817b2ef6e..3a82f3803bd 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -27,7 +27,7 @@ %span.gl-ml-5 = sprite_icon('users', css_class: 'gl-vertical-align-text-bottom') - = number_with_delimiter(group.users.count) + = number_with_delimiter(group.users_count) %span.gl-ml-5.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group) } = visibility_level_icon(group.visibility_level) diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index 960a4a93019..cb5277c02f0 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -49,7 +49,7 @@ = _('Gitaly Servers') = nav_link(controller: admin_analytics_nav_links) do - = link_to admin_dev_ops_score_path, data: { qa_selector: 'admin_analytics_link' } do + = link_to admin_dev_ops_report_path, data: { qa_selector: 'admin_analytics_link' } do .nav-icon-container = sprite_icon('chart') %span.nav-item-name @@ -57,12 +57,12 @@ %ul.sidebar-sub-level-items{ data: { qa_selector: 'admin_sidebar_analytics_submenu_content' } } = nav_link(controller: admin_analytics_nav_links, html_options: { class: "fly-out-top-item" }) do - = link_to admin_dev_ops_score_path do + = link_to admin_dev_ops_report_path do %strong.fly-out-top-item-name = _('Analytics') %li.divider.fly-out-top-item - = nav_link(controller: :dev_ops_score) do - = link_to admin_dev_ops_score_path, title: _('DevOps Report') do + = nav_link(controller: :dev_ops_report) do + = link_to admin_dev_ops_report_path, title: _('DevOps Report') do %span = _('DevOps Report') = nav_link(controller: :cohorts) do diff --git a/app/views/projects/static_site_editor/show.html.haml b/app/views/projects/static_site_editor/show.html.haml index 2d817912335..cbe27cefba3 100644 --- a/app/views/projects/static_site_editor/show.html.haml +++ b/app/views/projects/static_site_editor/show.html.haml @@ -1 +1 @@ -#static-site-editor{ data: @config.payload.merge({ merge_requests_illustration_path: image_path('illustrations/merge_requests.svg') }) } +#static-site-editor{ data: @data } diff --git a/app/views/shared/icons/_dev_ops_score_no_data.svg b/app/views/shared/icons/_dev_ops_report_no_data.svg index 5de929859ae..5de929859ae 100644 --- a/app/views/shared/icons/_dev_ops_score_no_data.svg +++ b/app/views/shared/icons/_dev_ops_report_no_data.svg diff --git a/app/views/shared/icons/_dev_ops_score_no_index.svg b/app/views/shared/icons/_dev_ops_report_no_index.svg index 0577efca93f..0577efca93f 100644 --- a/app/views/shared/icons/_dev_ops_score_no_index.svg +++ b/app/views/shared/icons/_dev_ops_report_no_index.svg diff --git a/app/views/shared/icons/_dev_ops_score_overview.svg b/app/views/shared/icons/_dev_ops_report_overview.svg index 2f31113bad7..2f31113bad7 100644 --- a/app/views/shared/icons/_dev_ops_score_overview.svg +++ b/app/views/shared/icons/_dev_ops_report_overview.svg diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index dc985f96e3f..28bf64841fc 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -133,6 +133,9 @@ = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar + - if issuable_sidebar[:supports_severity] + #js-severity + - if issuable_sidebar.dig(:features_available, :health_status) .js-sidebar-status-entry-point diff --git a/changelogs/unreleased/212274-remove-virtual-list-from-test-report.yml b/changelogs/unreleased/212274-remove-virtual-list-from-test-report.yml new file mode 100644 index 00000000000..e66665a4dd0 --- /dev/null +++ b/changelogs/unreleased/212274-remove-virtual-list-from-test-report.yml @@ -0,0 +1,5 @@ +--- +title: Remove virtual scroll list from pipeline test report +merge_request: 41935 +author: +type: fixed diff --git a/changelogs/unreleased/229402-incidents-severity-widget.yml b/changelogs/unreleased/229402-incidents-severity-widget.yml new file mode 100644 index 00000000000..f2003681c45 --- /dev/null +++ b/changelogs/unreleased/229402-incidents-severity-widget.yml @@ -0,0 +1,5 @@ +--- +title: Incident severity widget +merge_request: 39859 +author: +type: added diff --git a/changelogs/unreleased/Incident-highlight-bar.yml b/changelogs/unreleased/Incident-highlight-bar.yml new file mode 100644 index 00000000000..ddd746801cb --- /dev/null +++ b/changelogs/unreleased/Incident-highlight-bar.yml @@ -0,0 +1,5 @@ +--- +title: Incident highlight bar widget +merge_request: 41702 +author: +type: added diff --git a/changelogs/unreleased/dblessing-atlassian-integration-sign-in.yml b/changelogs/unreleased/dblessing-atlassian-integration-sign-in.yml new file mode 100644 index 00000000000..c3f204420db --- /dev/null +++ b/changelogs/unreleased/dblessing-atlassian-integration-sign-in.yml @@ -0,0 +1,5 @@ +--- +title: Add OmniAuth sign-in via Atlassian Cloud +merge_request: 40178 +author: +type: added diff --git a/changelogs/unreleased/i18n-admin-dashboard.yml b/changelogs/unreleased/i18n-admin-dashboard.yml new file mode 100644 index 00000000000..fa75f02e874 --- /dev/null +++ b/changelogs/unreleased/i18n-admin-dashboard.yml @@ -0,0 +1,5 @@ +--- +title: Externalize i18n strings from admin dashboard +merge_request: 41387 +author: Takuya Noguchi +type: other diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 7190c2c7331..b392b5a5448 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -1404,6 +1404,11 @@ test: app_id: 'YOUR_CLIENT_ID', app_secret: 'YOUR_CLIENT_SECRET' } + - { name: 'atlassian_oauth2', + app_id: 'YOUR_CLIENT_ID', + app_secret: 'YOUR_CLIENT_SECRET', + args: { scope: 'offline_access read:jira-user read:jira-work', prompt: 'consent' } + } ldap: enabled: false servers: diff --git a/config/routes.rb b/config/routes.rb index 481189c97c0..0512b7ca132 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -70,8 +70,8 @@ Rails.application.routes.draw do # Use this scope for all new global routes. scope path: '-' do # remove in 13.5 - get '/instance_statistics', to: redirect('admin/dev_ops_score') - get '/instance_statistics/dev_ops_score', to: redirect('admin/dev_ops_score') + get '/instance_statistics', to: redirect('admin/dev_ops_report') + get '/instance_statistics/dev_ops_score', to: redirect('admin/dev_ops_report') get '/instance_statistics/cohorts', to: redirect('admin/cohorts') # Autocomplete get '/autocomplete/users' => 'autocomplete#users' diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 258a6a04221..1dd1149a9d2 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -90,7 +90,9 @@ namespace :admin do resources :projects, only: [:index] resources :instance_statistics, only: :index - resource :dev_ops_score, controller: 'dev_ops_score', only: :show + resource :dev_ops_report, controller: 'dev_ops_report', only: :show + # remove in 13.5 + get '/dev_ops_score', to: redirect('admin/dev_ops_report') resources :cohorts, only: :index scope(path: 'projects/*namespace_id', diff --git a/db/fixtures/development/21_dev_ops_score_metrics.rb b/db/fixtures/development/21_dev_ops_report_metrics.rb index afea7fb4bd0..8b4956da37e 100644 --- a/db/fixtures/development/21_dev_ops_score_metrics.rb +++ b/db/fixtures/development/21_dev_ops_report_metrics.rb @@ -1,5 +1,5 @@ Gitlab::Seeder.quiet do - dev_ops_score_metric = DevOpsScore::Metric.new( + dev_ops_report_metric = DevOpsReport::Metric.new( leader_issues: 10.2, instance_issues: 3.2, @@ -31,10 +31,10 @@ Gitlab::Seeder.quiet do instance_service_desk_issues: 15.1 ) - if dev_ops_score_metric.save + if dev_ops_report_metric.save print '.' else - puts dev_ops_score_metric.errors.full_messages + puts dev_ops_report_metric.errors.full_messages print 'F' end end diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md index 60e1dfb4637..926a4abab7d 100644 --- a/doc/administration/auth/README.md +++ b/doc/administration/auth/README.md @@ -11,6 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w GitLab integrates with the following external authentication and authorization providers: +- [Atlassian](atlassian.md) - [Auth0](../../integration/auth0.md) - [Authentiq](authentiq.md) - [AWS Cognito](cognito.md) diff --git a/doc/administration/auth/atlassian.md b/doc/administration/auth/atlassian.md new file mode 100644 index 00000000000..3a1f5eeb0c2 --- /dev/null +++ b/doc/administration/auth/atlassian.md @@ -0,0 +1,86 @@ +--- +type: reference +stage: Manage +group: Access +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers +--- + +# Atlassian OmniAuth Provider + +To enable the Atlassian OmniAuth provider for passwordless authentication you must register an application with Atlassian. + +## Atlassian application registration + +1. Go to <https://developer.atlassian.com/apps/> and sign-in with the Atlassian + account that will administer the application. + +1. Click **Create a new app**. + +1. Choose an App Name, such as 'GitLab', and click **Create**. + +1. Note the `Client ID` and `Secret` for the [GitLab configuration](#gitlab-configuration) steps. + +1. In the left sidebar under **APIS AND FEATURES**, click **OAuth 2.0 (3LO)**. + +1. Enter the GitLab callback URL using the format `https://gitlab.example.com/users/auth/atlassian_oauth2/callback` and click **Save changes**. + +1. Click **+ Add** in the left sidebar under **APIS AND FEATURES**. + +1. Click **Add** for **Jira platform REST API** and then **Configure**. + +1. Click **Add** next to the following scopes: + - **View Jira issue data** + - **View user profiles** + - **Create and manage issues** + +## GitLab configuration + +1. On your GitLab server, open the configuration file: + + For Omnibus GitLab installations: + + ```shell + sudo editor /etc/gitlab/gitlab.rb + ``` + + For installations from source: + + ```shell + sudo -u git -H editor /home/git/gitlab/config/gitlab.yml + ``` + +1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings to enable single sign-on and add `atlassian_oauth2` as an OAuth provider. + +1. Add the provider configuration for Atlassian: + + For Omnibus GitLab installations: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + name: "atlassian_oauth2", + app_id: "YOUR_CLIENT_ID", + app_secret: "YOUR_CLIENT_SECRET", + args: { scope: 'offline_access read:jira-user read:jira-work', prompt: 'consent' } + } + ] + ``` + + For installations from source: + + ```yaml + - name: "atlassian_oauth2", + app_id: "YOUR_CLIENT_ID", + app_secret: "YOUR_CLIENT_SECRET", + args: { scope: 'offline_access read:jira-user read:jira-work', prompt: 'consent' } + ``` + +1. Change `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` to the Client credentials you received in [application registration](#atlassian-application-registration) steps. + +1. Save the configuration file. + +1. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect if you installed GitLab via Omnibus or from source respectively. + +On the sign-in page there should now be an Atlassian icon below the regular sign in form. Click the icon to begin the authentication process. + +If everything goes right, the user is signed in to GitLab using their Atlassian credentials. diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md index 1883f6659e6..c1230a81fb8 100644 --- a/doc/administration/packages/container_registry.md +++ b/doc/administration/packages/container_registry.md @@ -750,11 +750,15 @@ U = <user_id> # Get required details / objects user = User.find_by_id(U) project = Project.find_by_id(P) -repo = ContainerRepository.find_by(project_id: P) policy = ContainerExpirationPolicy.find_by(project_id: P) -# Start the tag cleanup -Projects::ContainerRepository::CleanupTagsService.new(project, user, policy.attributes.except("created_at", "updated_at")).execute(repo) +# Loop through each container repository +project.container_repositories.find_each do |repo| + puts repo.attributes + + # Start the tag cleanup + puts Projects::ContainerRepository::CleanupTagsService.new(project, user, policy.attributes.except("created_at", "updated_at")).execute(repo) +end ``` NOTE: **Note:** diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 210c10e0949..701b9f0b205 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -13089,6 +13089,41 @@ type Project { ): VulnerabilityConnection """ + Number of vulnerabilities per day for the project + """ + vulnerabilitiesCountByDay( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Last day for which to fetch vulnerability history + """ + endDate: ISO8601Date! + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + First day for which to fetch vulnerability history + """ + startDate: ISO8601Date! + ): VulnerabilitiesCountByDayConnection + + """ Vulnerability scanners reported on the project vulnerabilties """ vulnerabilityScanners( diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index 9729d8e799a..4ba643425ef 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -38172,6 +38172,87 @@ "deprecationReason": null }, { + "name": "vulnerabilitiesCountByDay", + "description": "Number of vulnerabilities per day for the project", + "args": [ + { + "name": "startDate", + "description": "First day for which to fetch vulnerability history", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ISO8601Date", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "endDate", + "description": "Last day for which to fetch vulnerability history", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ISO8601Date", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "after", + "description": "Returns the elements in the list that come after the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": "Returns the elements in the list that come before the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": "Returns the first _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": "Returns the last _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "VulnerabilitiesCountByDayConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "vulnerabilityScanners", "description": "Vulnerability scanners reported on the project vulnerabilties", "args": [ diff --git a/doc/ci/examples/artifactory_and_gitlab/index.md b/doc/ci/examples/artifactory_and_gitlab/index.md index c1b3ddec1b9..f5c86d2461d 100644 --- a/doc/ci/examples/artifactory_and_gitlab/index.md +++ b/doc/ci/examples/artifactory_and_gitlab/index.md @@ -3,12 +3,7 @@ stage: Verify group: Continuous Integration info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers disqus_identifier: 'https://docs.gitlab.com/ee/articles/artifactory_and_gitlab/index.html' -author: Fabio Busatto -author_gitlab: bikebilly -level: intermediate -article_type: tutorial type: tutorial -date: 2017-08-15 --- # How to deploy Maven projects to Artifactory with GitLab CI/CD diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md index e0d4f3f2402..b113b10f2e3 100644 --- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md +++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md @@ -2,14 +2,7 @@ stage: Release group: Progressive Delivery info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers -author: Dylan Griffith -author_gitlab: DylanGriffith -level: intermediate -article_type: tutorial type: tutorial -date: 2018-06-07 -last_updated: 2019-04-08 -description: "Continuous Deployment of a Spring Boot application to Cloud Foundry with GitLab CI/CD" --- # Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md index 35a35d97a4b..8a95c0b2258 100644 --- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md +++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md @@ -2,13 +2,7 @@ stage: Verify group: Continuous Integration info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers -author: Ryan Hall -author_gitlab: blitzgren -level: intermediate -article_type: tutorial type: tutorial -date: 2018-03-07 -last_updated: 2019-03-11 --- # DevOps and Game Dev with GitLab CI/CD diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md index b737b0d6b14..1f6c81a68aa 100644 --- a/doc/ci/examples/end_to_end_testing_webdriverio/index.md +++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md @@ -2,13 +2,7 @@ stage: Verify group: Testing info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers -author: Vincent Tunru -author_gitlab: Vinnl -level: advanced -article_type: user guide type: tutorial -date: 2019-02-18 -description: 'Confidence checking your entire app every time a new feature is added can quickly become repetitive. Learn how to automate it with GitLab CI/CD.' --- # End-to-end testing with GitLab CI/CD and WebdriverIO diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md index 3d41929f1f2..d6a829a4a44 100644 --- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md +++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md @@ -2,14 +2,7 @@ stage: Verify group: Continuous Integration info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers -disqus_identifier: 'https://docs.gitlab.com/ee/articles/laravel_with_gitlab_and_envoy/index.html' -author: Mehran Rasulian -author_gitlab: mehranrasulian -level: intermediate -article_type: tutorial type: tutorial -date: 2017-08-31 -last_updated: 2019-03-06 --- # Test and deploy Laravel applications with GitLab CI/CD and Envoy diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md index f2620461c09..0aed274fc82 100644 --- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md +++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md @@ -2,13 +2,7 @@ stage: Verify group: Continuous Integration info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers -author: Alexandre S Hostert -author_gitlab: Hostert -level: beginner -article_type: tutorial type: tutorial -date: 2018-02-20 -last_updated: 2019-03-06 --- # Testing a Phoenix application with GitLab CI/CD diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index 5ae9f8bc375..d8280316f19 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -2,7 +2,6 @@ stage: Verify group: Continuous Integration info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers -last_updated: 2017-12-13 type: tutorial --- diff --git a/doc/install/postgresql_extensions.md b/doc/install/postgresql_extensions.md index 4156d72097d..9e5a1e3d627 100644 --- a/doc/install/postgresql_extensions.md +++ b/doc/install/postgresql_extensions.md @@ -1,7 +1,3 @@ ---- -last_updated: 2020-09-01 ---- - # Managing PostgreSQL extensions This guide documents how to manage PostgreSQL extensions for installations with an external diff --git a/doc/topics/git/lfs/migrate_to_git_lfs.md b/doc/topics/git/lfs/migrate_to_git_lfs.md index 944f4d8f78d..f0ad7570d87 100644 --- a/doc/topics/git/lfs/migrate_to_git_lfs.md +++ b/doc/topics/git/lfs/migrate_to_git_lfs.md @@ -1,7 +1,5 @@ --- -type: tutorial, concepts description: "How to migrate an existing Git repository to Git LFS with BFG." -last_updated: 2019-07-11 --- # Migrate a Git repo into Git LFS with BFG diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index 9ad77a80d50..0759d45147a 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -1,7 +1,3 @@ ---- -last_updated: 2019-06-18 ---- - # Migrating from MySQL to PostgreSQL This guide documents how to take a working GitLab instance that uses MySQL and diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index bf4438fb518..830980f0997 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -9,12 +9,13 @@ module Gitlab module Access AccessDeniedError = Class.new(StandardError) - NO_ACCESS = 0 - GUEST = 10 - REPORTER = 20 - DEVELOPER = 30 - MAINTAINER = 40 - OWNER = 50 + NO_ACCESS = 0 + MINIMAL_ACCESS = 5 + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MAINTAINER = 40 + OWNER = 50 # Branch protection settings PROTECTION_NONE = 0 diff --git a/lib/gitlab/auth/atlassian/auth_hash.rb b/lib/gitlab/auth/atlassian/auth_hash.rb new file mode 100644 index 00000000000..047e4eabc51 --- /dev/null +++ b/lib/gitlab/auth/atlassian/auth_hash.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Gitlab + module Auth + module Atlassian + class AuthHash < Gitlab::Auth::OAuth::AuthHash + def token + credentials[:token] + end + + def refresh_token + credentials[:refresh_token] + end + + def expires? + credentials[:expires] + end + + def expires_at + credentials[:expires_at] + end + + private + + def credentials + auth_hash[:credentials] + end + end + end + end +end diff --git a/lib/gitlab/auth/atlassian/identity_linker.rb b/lib/gitlab/auth/atlassian/identity_linker.rb new file mode 100644 index 00000000000..4dec54d44d6 --- /dev/null +++ b/lib/gitlab/auth/atlassian/identity_linker.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Gitlab + module Auth + module Atlassian + class IdentityLinker < OmniauthIdentityLinkerBase + extend ::Gitlab::Utils::Override + include ::Gitlab::Utils::StrongMemoize + + private + + override :identity + def identity + strong_memoize(:identity) do + current_user.atlassian_identity || build_atlassian_identity + end + end + + def build_atlassian_identity + identity = current_user.build_atlassian_identity + ::Gitlab::Auth::Atlassian::User.assign_identity_from_auth_hash!(identity, auth_hash) + end + + def auth_hash + ::Gitlab::Auth::Atlassian::AuthHash.new(oauth) + end + end + end + end +end diff --git a/lib/gitlab/auth/atlassian/user.rb b/lib/gitlab/auth/atlassian/user.rb new file mode 100644 index 00000000000..6ab7741cc54 --- /dev/null +++ b/lib/gitlab/auth/atlassian/user.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Gitlab + module Auth + module Atlassian + class User < Gitlab::Auth::OAuth::User + def self.assign_identity_from_auth_hash!(identity, auth_hash) + identity.extern_uid = auth_hash.uid + identity.token = auth_hash.token + identity.refresh_token = auth_hash.refresh_token + identity.expires_at = Time.at(auth_hash.expires_at).utc.to_datetime if auth_hash.expires? + + identity + end + + protected + + def find_by_uid_and_provider + ::Atlassian::Identity.find_by_extern_uid(auth_hash.uid)&.user + end + + def add_or_update_user_identities + return unless gl_user + + identity = gl_user.atlassian_identity || gl_user.build_atlassian_identity + self.class.assign_identity_from_auth_hash!(identity, auth_hash) + end + + def auth_hash=(auth_hash) + @auth_hash = ::Gitlab::Auth::Atlassian::AuthHash.new(auth_hash) + end + end + end + end +end diff --git a/lib/gitlab/auth/o_auth/provider.rb b/lib/gitlab/auth/o_auth/provider.rb index 1ca59aa827b..1eae7af442d 100644 --- a/lib/gitlab/auth/o_auth/provider.rb +++ b/lib/gitlab/auth/o_auth/provider.rb @@ -5,10 +5,11 @@ module Gitlab module OAuth class Provider LABELS = { - "github" => "GitHub", - "gitlab" => "GitLab.com", - "google_oauth2" => "Google", - "azure_oauth2" => "Azure AD" + "github" => "GitHub", + "gitlab" => "GitLab.com", + "google_oauth2" => "Google", + "azure_oauth2" => "Azure AD", + 'atlassian_oauth2' => 'Atlassian' }.freeze def self.authentication(user, provider) diff --git a/lib/gitlab/pages/settings.rb b/lib/gitlab/pages/settings.rb index e3dbeee7b13..47f56072b37 100644 --- a/lib/gitlab/pages/settings.rb +++ b/lib/gitlab/pages/settings.rb @@ -7,11 +7,7 @@ module Gitlab def path if ::Gitlab::Runtime.web_server? && ENV['GITLAB_PAGES_DENY_DISK_ACCESS'] == '1' - begin - raise DiskAccessDenied - rescue DiskAccessDenied => ex - ::Gitlab::ErrorTracking.track_exception(ex) - end + raise DiskAccessDenied end super diff --git a/lib/gitlab/project_authorizations.rb b/lib/gitlab/project_authorizations.rb index ff90a009b2e..23e380b3cf1 100644 --- a/lib/gitlab/project_authorizations.rb +++ b/lib/gitlab/project_authorizations.rb @@ -99,6 +99,7 @@ module Gitlab .and(members[:source_type].eq('Namespace')) .and(members[:requested_at].eq(nil)) .and(members[:user_id].eq(user.id)) + .and(members[:access_level].gt(Gitlab::Access::MINIMAL_ACCESS)) Arel::Nodes::OuterJoin.new(members, Arel::Nodes::On.new(cond)) end @@ -119,6 +120,7 @@ module Gitlab .and(members[:source_type].eq('Namespace')) .and(members[:requested_at].eq(nil)) .and(members[:user_id].eq(user.id)) + .and(members[:access_level].gt(Gitlab::Access::MINIMAL_ACCESS)) Arel::Nodes::InnerJoin.new(members, Arel::Nodes::On.new(cond)) end diff --git a/lib/gitlab/static_site_editor/config.rb b/lib/gitlab/static_site_editor/config.rb deleted file mode 100644 index d335a434335..00000000000 --- a/lib/gitlab/static_site_editor/config.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module StaticSiteEditor - class Config - SUPPORTED_EXTENSIONS = %w[.md].freeze - - def initialize(repository, ref, file_path, return_url) - @repository = repository - @ref = ref - @file_path = file_path - @return_url = return_url - @commit_id = repository.commit(ref)&.id if ref - end - - def payload - { - branch: ref, - path: file_path, - commit_id: commit_id, - project_id: project.id, - project: project.path, - namespace: project.namespace.full_path, - return_url: sanitize_url(return_url), - is_supported_content: supported_content?.to_s, - base_url: Gitlab::Routing.url_helpers.project_show_sse_path(project, full_path) - } - end - - private - - attr_reader :repository, :ref, :file_path, :return_url, :commit_id - - delegate :project, to: :repository - - def supported_content? - master_branch? && extension_supported? && file_exists? - end - - def master_branch? - ref == 'master' - end - - def extension_supported? - return true if file_path.end_with?('.md.erb') && Feature.enabled?(:sse_erb_support, project) - - SUPPORTED_EXTENSIONS.any? { |ext| file_path.end_with?(ext) } - end - - def file_exists? - commit_id.present? && !repository.blob_at(commit_id, file_path).nil? - end - - def full_path - "#{ref}/#{file_path}" - end - - def sanitize_url(url) - url if Gitlab::UrlSanitizer.valid_web?(url) - end - end - end -end diff --git a/lib/gitlab/static_site_editor/config/combined_config.rb b/lib/gitlab/static_site_editor/config/combined_config.rb new file mode 100644 index 00000000000..387ce08e552 --- /dev/null +++ b/lib/gitlab/static_site_editor/config/combined_config.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Gitlab + module StaticSiteEditor + module Config + class CombinedConfig + def initialize(repository, ref, path, return_url) + @repository = repository + @ref = ref + @path = path + @return_url = return_url + end + + def data + generated_data = Gitlab::StaticSiteEditor::Config::GeneratedConfig.new( + @repository, + @ref, + @path, + @return_url + ).data + file_data = Gitlab::StaticSiteEditor::Config::FileConfig.new.data + check_for_duplicate_keys(generated_data, file_data) + generated_data.merge(file_data) + end + + private + + def check_for_duplicate_keys(generated_data, file_data) + duplicate_keys = generated_data.keys & file_data.keys + raise StandardError.new("Duplicate key(s) '#{duplicate_keys}' found.") if duplicate_keys.present? + end + end + end + end +end diff --git a/lib/gitlab/static_site_editor/config/file_config.rb b/lib/gitlab/static_site_editor/config/file_config.rb new file mode 100644 index 00000000000..b43a5440e6b --- /dev/null +++ b/lib/gitlab/static_site_editor/config/file_config.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Gitlab + module StaticSiteEditor + module Config + class FileConfig + def data + merge_requests_illustration_path = ActionController::Base.helpers.image_path('illustrations/merge_requests.svg') + { + merge_requests_illustration_path: merge_requests_illustration_path + } + end + end + end + end +end diff --git a/lib/gitlab/static_site_editor/config/generated_config.rb b/lib/gitlab/static_site_editor/config/generated_config.rb new file mode 100644 index 00000000000..4c875b1d211 --- /dev/null +++ b/lib/gitlab/static_site_editor/config/generated_config.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Gitlab + module StaticSiteEditor + module Config + class GeneratedConfig + SUPPORTED_EXTENSIONS = %w[.md].freeze + + def initialize(repository, ref, path, return_url) + @repository = repository + @ref = ref + @path = path + @return_url = return_url + end + + def data + { + branch: ref, + path: path, + commit_id: commit_id, + project_id: project.id, + project: project.path, + namespace: project.namespace.full_path, + return_url: sanitize_url(return_url), + is_supported_content: supported_content?.to_s, + base_url: Gitlab::Routing.url_helpers.project_show_sse_path(project, full_path) + } + end + + private + + attr_reader :repository, :ref, :path, :return_url + + delegate :project, to: :repository + + def commit_id + repository.commit(ref)&.id if ref + end + + def supported_content? + master_branch? && extension_supported? && file_exists? + end + + def master_branch? + ref == 'master' + end + + def extension_supported? + return true if path.end_with?('.md.erb') && Feature.enabled?(:sse_erb_support, project) + + SUPPORTED_EXTENSIONS.any? { |ext| path.end_with?(ext) } + end + + def file_exists? + commit_id.present? && !repository.blob_at(commit_id, path).nil? + end + + def full_path + "#{ref}/#{path}" + end + + def sanitize_url(url) + url if Gitlab::UrlSanitizer.valid_web?(url) + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a43e40daecf..ce0c7e71203 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1776,21 +1776,51 @@ msgstr "" msgid "AdminArea|Bots" msgstr "" +msgid "AdminArea|Components" +msgstr "" + msgid "AdminArea|Developer" msgstr "" +msgid "AdminArea|Features" +msgstr "" + +msgid "AdminArea|Groups: %{number_of_groups}" +msgstr "" + msgid "AdminArea|Guest" msgstr "" msgid "AdminArea|Included Free in license" msgstr "" +msgid "AdminArea|Latest groups" +msgstr "" + +msgid "AdminArea|Latest projects" +msgstr "" + +msgid "AdminArea|Latest users" +msgstr "" + msgid "AdminArea|Maintainer" msgstr "" +msgid "AdminArea|New group" +msgstr "" + +msgid "AdminArea|New project" +msgstr "" + +msgid "AdminArea|New user" +msgstr "" + msgid "AdminArea|Owner" msgstr "" +msgid "AdminArea|Projects: %{number_of_projects}" +msgstr "" + msgid "AdminArea|Reporter" msgstr "" @@ -1818,6 +1848,9 @@ msgstr "" msgid "AdminArea|Users without a Group and Project" msgstr "" +msgid "AdminArea|Users: %{number_of_users}" +msgstr "" + msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" @@ -10772,7 +10805,7 @@ msgstr "" msgid "FeatureFlags|Include additional user IDs" msgstr "" -msgid "FeatureFlags|Install a %{docs_link_anchored_start}compatible client library%{docs_link_anchored_end} and specify the API URL, application name, and instance ID during the configuration setup. %{docs_link_start}More Information%{docs_link_end}" +msgid "FeatureFlags|Install a %{docsLinkAnchoredStart}compatible client library%{docsLinkAnchoredEnd} and specify the API URL, application name, and instance ID during the configuration setup. %{docsLinkStart}More Information%{docsLinkEnd}" msgstr "" msgid "FeatureFlags|Instance ID" @@ -11696,9 +11729,15 @@ msgstr "" msgid "GitHub import" msgstr "" +msgid "GitLab" +msgstr "" + msgid "GitLab / Unsubscribe" msgstr "" +msgid "GitLab API" +msgstr "" + msgid "GitLab Enterprise Edition %{plan}" msgstr "" @@ -11711,12 +11750,18 @@ msgstr "" msgid "GitLab Issue" msgstr "" +msgid "GitLab Pages" +msgstr "" + msgid "GitLab Service Desk is a simple way to allow people to create issues in your GitLab instance without needing their own user account. It provides a unique email address for end users to create issues in a project, and replies can be sent either through the GitLab interface or by email. End users will only see the thread through email." msgstr "" msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)." msgstr "" +msgid "GitLab Shell" +msgstr "" + msgid "GitLab Support Bot" msgstr "" @@ -11726,6 +11771,9 @@ msgstr "" msgid "GitLab User" msgstr "" +msgid "GitLab Workhorse" +msgstr "" + msgid "GitLab allows you to continue using your license even if you exceed the number of seats you purchased. You will be required to pay for these seats when you renew your license." msgstr "" @@ -12807,6 +12855,15 @@ msgstr "" msgid "Highest role:" msgstr "" +msgid "HighlightBar|Alert events:" +msgstr "" + +msgid "HighlightBar|Alert start time:" +msgstr "" + +msgid "HighlightBar|Original alert:" +msgstr "" + msgid "History" msgstr "" @@ -13252,18 +13309,30 @@ msgstr "" msgid "IncidentManagement|Create incident" msgstr "" +msgid "IncidentManagement|Critical - S1" +msgstr "" + msgid "IncidentManagement|Date created" msgstr "" msgid "IncidentManagement|Display your incidents in a dedicated view" msgstr "" +msgid "IncidentManagement|High - S2" +msgstr "" + msgid "IncidentManagement|Incident" msgstr "" msgid "IncidentManagement|Incidents" msgstr "" +msgid "IncidentManagement|Low - S4" +msgstr "" + +msgid "IncidentManagement|Medium - S3" +msgstr "" + msgid "IncidentManagement|No incidents to display." msgstr "" @@ -13285,6 +13354,9 @@ msgstr "" msgid "IncidentManagement|Unassigned" msgstr "" +msgid "IncidentManagement|Unknown" +msgstr "" + msgid "IncidentManagement|Unpublished" msgstr "" @@ -20487,6 +20559,9 @@ msgstr "" msgid "README" msgstr "" +msgid "Rails" +msgstr "" + msgid "Rake Tasks Help" msgstr "" @@ -21513,6 +21588,9 @@ msgstr "" msgid "Rook" msgstr "" +msgid "Ruby" +msgstr "" + msgid "Rule name is already taken." msgstr "" @@ -22934,6 +23012,15 @@ msgstr "" msgid "Severity" msgstr "" +msgid "SeverityWidget|Severity" +msgstr "" + +msgid "SeverityWidget|Severity: %{severity}" +msgstr "" + +msgid "SeverityWidget|There was an error while updating severity." +msgstr "" + msgid "Shards (%{shards})" msgstr "" diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb index f012370f8dd..90b438cacae 100644 --- a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb @@ -22,7 +22,7 @@ module QA end end - it 'publishes a maven package and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/943' do + it 'publishes a maven package and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/943', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/247281', type: :investigating } do uri = URI.parse(Runtime::Scenario.gitlab_address) gitlab_address_with_port = "#{uri.scheme}://#{uri.host}:#{uri.port}" pom_xml = { diff --git a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb index 3f714b96917..e97ede35610 100644 --- a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb @@ -21,7 +21,7 @@ module QA end end - it 'publishes an npm package and then deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/944' do + it 'publishes an npm package and then deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/944', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/247281', type: :investigating } do uri = URI.parse(Runtime::Scenario.gitlab_address) gitlab_host_with_port = "#{uri.host}:#{uri.port}" gitlab_address_with_port = "#{uri.scheme}://#{uri.host}:#{uri.port}" diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb index d2408ab6fe2..fe2d821576b 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb @@ -45,7 +45,7 @@ module QA Flow::Login.sign_in end - it 'works with Auto DevOps' do + it 'works with Auto DevOps', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/240946', type: :flaky } do %w[build code_quality test].each do |job| pipeline.visit! diff --git a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb index a0f613cfda2..8dca0d6698e 100644 --- a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb +++ b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb @@ -20,7 +20,7 @@ module QA end end - it 'shows results for the original request and AJAX requests', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/478' do + it 'shows results for the original request and AJAX requests', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/478', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/247467', type: :investigating } do # Issue pages always make AJAX requests Resource::Issue.fabricate_via_browser_ui! do |issue| issue.title = 'Performance bar test' diff --git a/rubocop/cop/avoid_route_redirect_leading_slash.rb b/rubocop/cop/avoid_route_redirect_leading_slash.rb index d66e434dc9c..591c003a166 100644 --- a/rubocop/cop/avoid_route_redirect_leading_slash.rb +++ b/rubocop/cop/avoid_route_redirect_leading_slash.rb @@ -7,10 +7,10 @@ module RuboCop # # @example # # bad - # root to: redirect('/-/instance/statistics/dev_ops_score') + # root to: redirect('/-/instance_statistics/dev_ops_report') # # # good - # root to: redirect('-/instance/statistics/dev_ops_score') + # root to: redirect('-/instance_statistics/dev_ops_report') # class AvoidRouteRedirectLeadingSlash < RuboCop::Cop::Cop diff --git a/spec/controllers/admin/dev_ops_score_controller_spec.rb b/spec/controllers/admin/dev_ops_report_controller_spec.rb index dec48340eb6..e69f8bf7462 100644 --- a/spec/controllers/admin/dev_ops_score_controller_spec.rb +++ b/spec/controllers/admin/dev_ops_report_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::DevOpsScoreController do +RSpec.describe Admin::DevOpsReportController do describe 'GET #show' do context 'as admin' do let(:user) { create(:admin) } diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index 3f7f0c55f38..de6edbe936d 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -276,6 +276,51 @@ RSpec.describe OmniauthCallbacksController, type: :controller do end end + context 'atlassian_oauth2' do + let(:provider) { :atlassian_oauth2 } + let(:extern_uid) { 'my-uid' } + + context 'when the user and identity already exist' do + let(:user) { create(:atlassian_user, extern_uid: extern_uid) } + + it 'allows sign-in' do + post :atlassian_oauth2 + + expect(request.env['warden']).to be_authenticated + end + end + + context 'for a new user' do + before do + stub_omniauth_setting(enabled: true, auto_link_user: true, allow_single_sign_on: ['atlassian_oauth2']) + + user.destroy + end + + it 'denies sign-in if sign-up is enabled, but block_auto_created_users is set' do + post :atlassian_oauth2 + + expect(flash[:alert]).to start_with 'Your account has been blocked.' + end + + it 'accepts sign-in if sign-up is enabled' do + stub_omniauth_setting(block_auto_created_users: false) + + post :atlassian_oauth2 + + expect(request.env['warden']).to be_authenticated + end + + it 'denies sign-in if sign-up is not enabled' do + stub_omniauth_setting(allow_single_sign_on: false, block_auto_created_users: false) + + post :atlassian_oauth2 + + expect(flash[:alert]).to start_with 'Signing in using your Atlassian account without a pre-existing GitLab account is not allowed.' + end + end + end + context 'salesforce' do let(:extern_uid) { 'my-uid' } let(:provider) { :salesforce } diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb index 52a7a1609a1..c6e7866a659 100644 --- a/spec/controllers/profiles/accounts_controller_spec.rb +++ b/spec/controllers/profiles/accounts_controller_spec.rb @@ -45,5 +45,18 @@ RSpec.describe Profiles::AccountsController do end end end + + describe 'atlassian_oauth2 provider' do + let(:user) { create(:atlassian_user) } + + it 'allows a user to unlink a connected account' do + expect(user.atlassian_identity).not_to be_nil + + delete :unlink, params: { provider: 'atlassian_oauth2' } + + expect(response).to have_gitlab_http_status(:found) + expect(user.reload.atlassian_identity).to be_nil + end + end end end diff --git a/spec/controllers/projects/static_site_editor_controller_spec.rb b/spec/controllers/projects/static_site_editor_controller_spec.rb index 384218504b9..1156887b2b6 100644 --- a/spec/controllers/projects/static_site_editor_controller_spec.rb +++ b/spec/controllers/projects/static_site_editor_controller_spec.rb @@ -5,6 +5,13 @@ require 'spec_helper' RSpec.describe Projects::StaticSiteEditorController do let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:user) { create(:user) } + let(:data) { instance_double(Hash) } + + before do + allow_next_instance_of(Gitlab::StaticSiteEditor::Config::CombinedConfig) do |config| + allow(config).to receive(:data) { data } + end + end describe 'GET show' do let(:default_params) do @@ -55,12 +62,12 @@ RSpec.describe Projects::StaticSiteEditorController do end it 'assigns a required variables' do - expect(assigns(:config)).to be_a(Gitlab::StaticSiteEditor::Config) + expect(assigns(:data)).to eq(data) expect(assigns(:ref)).to eq('master') expect(assigns(:path)).to eq('README.md') end - context 'when combination of ref and file path is incorrect' do + context 'when combination of ref and path is incorrect' do let(:default_params) { super().merge(id: 'unknown') } it 'responds with 404 page' do diff --git a/spec/factories/dev_ops_score_metrics.rb b/spec/factories/dev_ops_report_metrics.rb index 1d1f1a2c39e..808c70c2499 100644 --- a/spec/factories/dev_ops_score_metrics.rb +++ b/spec/factories/dev_ops_report_metrics.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :dev_ops_score_metric, class: 'DevOpsScore::Metric' do + factory :dev_ops_report_metric, class: 'DevOpsReport::Metric' do leader_issues { 9.256 } instance_issues { 1.234 } percentage_issues { 13.331 } diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb index a6706ed4145..37ddbc09616 100644 --- a/spec/factories/group_members.rb +++ b/spec/factories/group_members.rb @@ -28,5 +28,11 @@ FactoryBot.define do trait :blocked do after(:build) { |group_member, _| group_member.user.block! } end + + trait :minimal_access do + to_create { |instance| instance.save!(validate: false) } + + access_level { GroupMember::MINIMAL_ACCESS } + end end end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index d4b2ac5f056..1a8c5d7e40c 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -136,6 +136,16 @@ FactoryBot.define do end end + factory :atlassian_user do + transient do + extern_uid { generate(:username) } + end + + after(:create) do |user, evaluator| + create(:atlassian_identity, user: user, extern_uid: evaluator.extern_uid) + end + end + factory :admin, traits: [:admin] end end diff --git a/spec/features/admin/admin_dev_ops_score_spec.rb b/spec/features/admin/admin_dev_ops_report_spec.rb index 1d3e7deef65..c201011cbea 100644 --- a/spec/features/admin/admin_dev_ops_score_spec.rb +++ b/spec/features/admin/admin_dev_ops_report_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'DevOps Report page' do end it 'has dismissable intro callout', :js do - visit admin_dev_ops_score_path + visit admin_dev_ops_report_path expect(page).to have_content 'Introducing Your DevOps Report' @@ -23,13 +23,13 @@ RSpec.describe 'DevOps Report page' do end it 'shows empty state', :js do - visit admin_dev_ops_score_path + visit admin_dev_ops_report_path expect(page).to have_selector(".js-empty-state") end it 'hides the intro callout' do - visit admin_dev_ops_score_path + visit admin_dev_ops_report_path expect(page).not_to have_content 'Introducing Your DevOps Report' end @@ -39,7 +39,7 @@ RSpec.describe 'DevOps Report page' do it 'shows empty state' do stub_application_setting(usage_ping_enabled: true) - visit admin_dev_ops_score_path + visit admin_dev_ops_report_path expect(page).to have_content('Data is still calculating') end @@ -48,9 +48,9 @@ RSpec.describe 'DevOps Report page' do context 'when there is data to display' do it 'shows numbers for each metric' do stub_application_setting(usage_ping_enabled: true) - create(:dev_ops_score_metric) + create(:dev_ops_report_metric) - visit admin_dev_ops_score_path + visit admin_dev_ops_report_path expect(page).to have_content( 'Issues created per active user 1.2 You 9.3 Lead 13.3%' diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb index 68b120db227..67e7de5921a 100644 --- a/spec/finders/group_members_finder_spec.rb +++ b/spec/finders/group_members_finder_spec.rb @@ -16,6 +16,7 @@ RSpec.describe GroupMembersFinder, '#execute' do member1 = group.add_maintainer(user1) member2 = group.add_maintainer(user2) member3 = group.add_maintainer(user3) + create(:group_member, :minimal_access, user: create(:user), source: group) result = described_class.new(group).execute diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb index 78764f79a6c..48e4c5dadc9 100644 --- a/spec/finders/groups_finder_spec.rb +++ b/spec/finders/groups_finder_spec.rb @@ -150,6 +150,14 @@ RSpec.describe GroupsFinder do end end end + + context 'being minimal access member of parent group' do + it 'do not return group with minimal_access access' do + create(:group_member, :minimal_access, user: user, source: parent_group) + + is_expected.to contain_exactly(public_subgroup, internal_subgroup) + end + end end end end diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb index 3ef8d6a01aa..3530858e2de 100644 --- a/spec/finders/members_finder_spec.rb +++ b/spec/finders/members_finder_spec.rb @@ -45,6 +45,18 @@ RSpec.describe MembersFinder, '#execute' do expect(result).to contain_exactly(member1) end + it 'does not return members of parent group with minimal access' do + nested_group.request_access(user1) + member1 = group.add_maintainer(user2) + member2 = nested_group.add_maintainer(user3) + member3 = project.add_maintainer(user4) + create(:group_member, :minimal_access, user: create(:user), source: group) + + result = described_class.new(project, user2).execute + + expect(result).to contain_exactly(member1, member2, member3) + end + it 'includes only non-invite members if user do not have amdin permissions on project' do create(:project_member, :invited, project: project, invite_email: create(:user).email) member1 = project.add_maintainer(user1) diff --git a/spec/fixtures/api/schemas/entities/issue_sidebar.json b/spec/fixtures/api/schemas/entities/issue_sidebar.json index b2c8244ce69..717eb4992ea 100644 --- a/spec/fixtures/api/schemas/entities/issue_sidebar.json +++ b/spec/fixtures/api/schemas/entities/issue_sidebar.json @@ -44,6 +44,7 @@ "move_issue_path": { "type": "string" }, "projects_autocomplete_path": { "type": "string" }, "supports_time_tracking": { "type": "boolean" }, - "supports_milestone": { "type": "boolean" } + "supports_milestone": { "type": "boolean" }, + "supports_severity": { "type": "boolean" } } } diff --git a/spec/frontend/issue_show/components/app_spec.js b/spec/frontend/issue_show/components/app_spec.js index 69f1d5aeec2..c5e94aa3376 100644 --- a/spec/frontend/issue_show/components/app_spec.js +++ b/spec/frontend/issue_show/components/app_spec.js @@ -14,7 +14,7 @@ import { secondRequest, zoomMeetingUrl, } from '../mock_data'; -import IncidentTabs from '~/issue_show/components/incident_tabs.vue'; +import IncidentTabs from '~/issue_show/components/incidents/incident_tabs.vue'; import DescriptionComponent from '~/issue_show/components/description.vue'; import PinnedLinks from '~/issue_show/components/pinned_links.vue'; @@ -39,6 +39,13 @@ describe('Issuable output', () => { const mountComponent = (props = {}) => { wrapper = mount(IssuableApp, { propsData: { ...appProps, ...props }, + provide: { + fullPath: 'gitlab-org/incidents', + iid: '19', + }, + stubs: { + HighlightBar: true, + }, }); }; diff --git a/spec/frontend/issue_show/components/highlight_bar_spec.js b/spec/frontend/issue_show/components/highlight_bar_spec.js new file mode 100644 index 00000000000..8a0b3a997e1 --- /dev/null +++ b/spec/frontend/issue_show/components/highlight_bar_spec.js @@ -0,0 +1,60 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLink } from '@gitlab/ui'; +import HighlightBar from '~/issue_show/components/incidents/highlight_bar/higlight_bar.vue'; +import { formatDate } from '~/lib/utils/datetime_utility'; + +jest.mock('~/lib/utils/datetime_utility'); + +describe('Highlight Bar', () => { + let wrapper; + + const alert = { + createdAt: '2020-05-29T10:39:22Z', + detailsUrl: 'http://127.0.0.1:3000/root/unique-alerts/-/alert_management/1/details', + eventCount: 1, + title: 'Alert 1', + }; + + const mountComponent = () => { + wrapper = shallowMount(HighlightBar, { + provide: { + fullPath: 'project/id', + iid: '1', + }, + data() { + return { alert }; + }, + }); + }; + + beforeEach(() => { + mountComponent(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + const findLink = () => wrapper.find(GlLink); + + it('renders a link to the alert page', () => { + expect(findLink().exists()).toBe(true); + expect(findLink().attributes('href')).toBe(alert.detailsUrl); + expect(findLink().text()).toContain(alert.title); + }); + + it('renders formatted start time of the alert', () => { + const formattedDate = '2020-05-29 UTC'; + formatDate.mockReturnValueOnce(formattedDate); + mountComponent(); + expect(formatDate).toHaveBeenCalledWith(alert.createdAt, 'yyyy-mm-dd Z'); + expect(wrapper.text()).toContain(formattedDate); + }); + + it('renders a number of alert events', () => { + expect(wrapper.text()).toContain(alert.eventCount); + }); +}); diff --git a/spec/frontend/issue_show/components/incident_tabs_spec.js b/spec/frontend/issue_show/components/incident_tabs_spec.js index 19f75345450..0186b01b093 100644 --- a/spec/frontend/issue_show/components/incident_tabs_spec.js +++ b/spec/frontend/issue_show/components/incident_tabs_spec.js @@ -1,8 +1,9 @@ import { shallowMount } from '@vue/test-utils'; import { GlTab } from '@gitlab/ui'; -import IncidentTabs from '~/issue_show/components/incident_tabs.vue'; +import IncidentTabs from '~/issue_show/components/incidents/incident_tabs.vue'; import { descriptionProps } from '../mock_data'; import DescriptionComponent from '~/issue_show/components/description.vue'; +import HighlightBar from '~/issue_show/components/incidents/highlight_bar/higlight_bar.vue'; describe('Incident Tabs component', () => { let wrapper; @@ -25,6 +26,7 @@ describe('Incident Tabs component', () => { const findTabs = () => wrapper.findAll(GlTab); const findSummaryTab = () => findTabs().at(0); const findDescriptionComponent = () => wrapper.find(DescriptionComponent); + const findHighlightBarComponent = () => wrapper.find(HighlightBar); describe('default state', () => { it('renders the summary tab', async () => { @@ -33,8 +35,9 @@ describe('Incident Tabs component', () => { expect(findSummaryTab().attributes('title')).toBe('Summary'); }); - it('renders the description component', () => { + it('renders the description component with highlight bar', () => { expect(findDescriptionComponent().exists()).toBe(true); + expect(findHighlightBarComponent().exists()).toBe(true); }); it('passes all props to the description component', () => { diff --git a/spec/frontend/sidebar/components/severity/severity_spec.js b/spec/frontend/sidebar/components/severity/severity_spec.js new file mode 100644 index 00000000000..b6690f11d6b --- /dev/null +++ b/spec/frontend/sidebar/components/severity/severity_spec.js @@ -0,0 +1,57 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; +import SeverityToken from '~/sidebar/components/severity/severity.vue'; +import { INCIDENT_SEVERITY } from '~/sidebar/components/severity/constants'; + +describe('SeverityToken', () => { + let wrapper; + + function createComponent(props) { + wrapper = shallowMount(SeverityToken, { + propsData: { + ...props, + }, + }); + } + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + const findIcon = () => wrapper.find(GlIcon); + + it('renders severity token for each severity type', () => { + Object.values(INCIDENT_SEVERITY).forEach(severity => { + createComponent({ severity }); + expect(findIcon().classes()).toContain(`icon-${severity.icon}`); + expect(findIcon().attributes('name')).toBe(`severity-${severity.icon}`); + expect(wrapper.text()).toBe(severity.label); + }); + }); + + it('renders only icon when `iconOnly` prop is set to `true`', () => { + const severity = INCIDENT_SEVERITY.CRITICAL; + createComponent({ severity, iconOnly: true }); + expect(findIcon().classes()).toContain(`icon-${severity.icon}`); + expect(findIcon().attributes('name')).toBe(`severity-${severity.icon}`); + expect(wrapper.text()).toBe(''); + }); + + describe('icon size', () => { + it('renders the icon in default size when other is not specified', () => { + const severity = INCIDENT_SEVERITY.HIGH; + createComponent({ severity }); + expect(findIcon().attributes('size')).toBe('12'); + }); + + it('renders the icon in provided size', () => { + const severity = INCIDENT_SEVERITY.HIGH; + const iconSize = 14; + createComponent({ severity, iconSize }); + expect(findIcon().attributes('size')).toBe(`${iconSize}`); + }); + }); +}); diff --git a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js new file mode 100644 index 00000000000..638d3706d12 --- /dev/null +++ b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js @@ -0,0 +1,166 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlTooltip, GlSprintf } from '@gitlab/ui'; +import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; +import SidebarSeverity from '~/sidebar/components/severity/sidebar_severity.vue'; +import SeverityToken from '~/sidebar/components/severity/severity.vue'; +import updateIssuableSeverity from '~/sidebar/components/severity/graphql/mutations/update_issuable_severity.mutation.graphql'; +import { INCIDENT_SEVERITY, ISSUABLE_TYPES } from '~/sidebar/components/severity/constants'; + +jest.mock('~/flash'); + +describe('SidebarSeverity', () => { + let wrapper; + let mutate; + const projectPath = 'gitlab-org/gitlab-test'; + const iid = '1'; + const severity = 'CRITICAL'; + + function createComponent(props = {}) { + const propsData = { + projectPath, + iid, + issuableType: ISSUABLE_TYPES.INCIDENT, + initialSeverity: severity, + ...props, + }; + mutate = jest.fn(); + wrapper = shallowMount(SidebarSeverity, { + propsData, + mocks: { + $apollo: { + mutate, + }, + }, + stubs: { + GlSprintf, + }, + }); + } + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + const findSeverityToken = () => wrapper.findAll(SeverityToken); + const findEditBtn = () => wrapper.find('[data-testid="editButton"]'); + const findDropdown = () => wrapper.find(GlDropdown); + const findCriticalSeverityDropdownItem = () => wrapper.find(GlDropdownItem); + const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findTooltip = () => wrapper.find(GlTooltip); + const findCollapsedSeverity = () => wrapper.find({ ref: 'severity' }); + + it('renders severity widget', () => { + expect(findEditBtn().exists()).toBe(true); + expect(findSeverityToken().exists()).toBe(true); + expect(findDropdown().exists()).toBe(true); + }); + + describe('Update severity', () => { + it('calls `$apollo.mutate` with `updateIssuableSeverity`', () => { + jest + .spyOn(wrapper.vm.$apollo, 'mutate') + .mockResolvedValueOnce({ data: { issueSetSeverity: { issue: { severity } } } }); + + findCriticalSeverityDropdownItem().vm.$emit('click'); + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: updateIssuableSeverity, + variables: { + iid, + projectPath, + severity, + }, + }); + }); + + it('shows error alert when severity update fails ', () => { + const errorMsg = 'Something went wrong'; + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValueOnce(errorMsg); + findCriticalSeverityDropdownItem().vm.$emit('click'); + + setImmediate(() => { + expect(createFlash).toHaveBeenCalled(); + }); + }); + + it('shows loading icon while updating', async () => { + let resolvePromise; + wrapper.vm.$apollo.mutate = jest.fn( + () => + new Promise(resolve => { + resolvePromise = resolve; + }), + ); + findCriticalSeverityDropdownItem().vm.$emit('click'); + + await wrapper.vm.$nextTick(); + expect(findLoadingIcon().exists()).toBe(true); + + resolvePromise(); + await waitForPromises(); + expect(findLoadingIcon().exists()).toBe(false); + }); + }); + + describe('Switch between collapsed/expanded view of the sidebar', () => { + const HIDDDEN_CLASS = 'gl-display-none'; + const SHOWN_CLASS = 'show'; + + describe('collapsed', () => { + it('should have collapsed icon class', () => { + expect(findCollapsedSeverity().classes('sidebar-collapsed-icon')).toBe(true); + }); + + it('should display only icon with a tooltip', () => { + expect( + findSeverityToken() + .at(0) + .attributes('icononly'), + ).toBe('true'); + expect( + findSeverityToken() + .at(0) + .attributes('iconsize'), + ).toBe('14'); + expect( + findTooltip() + .text() + .replace(/\s+/g, ' '), + ).toContain(`Severity: ${INCIDENT_SEVERITY[severity].label}`); + }); + + it('should expand the dropdown on collapsed icon click', async () => { + wrapper.vm.isDropdownShowing = false; + await wrapper.vm.$nextTick(); + expect(findDropdown().classes(HIDDDEN_CLASS)).toBe(true); + + findCollapsedSeverity().trigger('click'); + await wrapper.vm.$nextTick(); + expect(findDropdown().classes(SHOWN_CLASS)).toBe(true); + }); + }); + + describe('expanded', () => { + it('toggles dropdown with edit button', async () => { + wrapper.vm.isDropdownShowing = false; + await wrapper.vm.$nextTick(); + expect(findDropdown().classes(HIDDDEN_CLASS)).toBe(true); + + findEditBtn().vm.$emit('click'); + await wrapper.vm.$nextTick(); + expect(findDropdown().classes(SHOWN_CLASS)).toBe(true); + + findEditBtn().vm.$emit('click'); + await wrapper.vm.$nextTick(); + expect(findDropdown().classes(HIDDDEN_CLASS)).toBe(true); + }); + }); + }); +}); diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index 1e843ee221b..e0d316baa17 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -220,4 +220,44 @@ RSpec.describe AuthHelper do it { is_expected.to be(false) } end end + + describe '#auth_active?' do + let(:user) { create(:user) } + + def auth_active? + helper.auth_active?(provider) + end + + before do + allow(helper).to receive(:current_user).and_return(user) + end + + context 'for atlassian_oauth2 provider' do + let_it_be(:provider) { :atlassian_oauth2 } + + it 'returns true when present' do + create(:atlassian_identity, user: user) + + expect(auth_active?).to be true + end + + it 'returns false when not present' do + expect(auth_active?).to be false + end + end + + context 'for other omniauth providers' do + let_it_be(:provider) { 'google_oauth2' } + + it 'returns true when present' do + create(:identity, provider: provider, user: user) + + expect(auth_active?).to be true + end + + it 'returns false when not present' do + expect(auth_active?).to be false + end + end + end end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 8e83bd0ee9d..1f47ec4bf97 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -198,7 +198,7 @@ RSpec.describe IssuablesHelper do initialDescriptionHtml: '<p dir="auto">issue text</p>', initialDescriptionText: 'issue text', initialTaskStatus: '0 of 0 tasks completed', - issueType: 'issue' + iid: issue.iid.to_s } expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data)) end diff --git a/spec/lib/gitlab/auth/atlassian/auth_hash_spec.rb b/spec/lib/gitlab/auth/atlassian/auth_hash_spec.rb new file mode 100644 index 00000000000..c57b15361c4 --- /dev/null +++ b/spec/lib/gitlab/auth/atlassian/auth_hash_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Auth::Atlassian::AuthHash do + let(:auth_hash) do + described_class.new( + OmniAuth::AuthHash.new(uid: 'john', credentials: credentials) + ) + end + + let(:credentials) do + { + token: 'super_secret_token', + refresh_token: 'super_secret_refresh_token', + expires_at: 2.weeks.from_now.to_i, + expires: true + } + end + + describe '#uid' do + it 'returns the correct uid' do + expect(auth_hash.uid).to eq('john') + end + end + + describe '#token' do + it 'returns the correct token' do + expect(auth_hash.token).to eq(credentials[:token]) + end + end + + describe '#refresh_token' do + it 'returns the correct refresh token' do + expect(auth_hash.refresh_token).to eq(credentials[:refresh_token]) + end + end + + describe '#token' do + it 'returns the correct expires boolean' do + expect(auth_hash.expires?).to eq(credentials[:expires]) + end + end + + describe '#token' do + it 'returns the correct expiration' do + expect(auth_hash.expires_at).to eq(credentials[:expires_at]) + end + end +end diff --git a/spec/lib/gitlab/auth/atlassian/identity_linker_spec.rb b/spec/lib/gitlab/auth/atlassian/identity_linker_spec.rb new file mode 100644 index 00000000000..ca6b91ac6f1 --- /dev/null +++ b/spec/lib/gitlab/auth/atlassian/identity_linker_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Auth::Atlassian::IdentityLinker do + let(:user) { create(:user) } + let(:extern_uid) { generate(:username) } + let(:oauth) do + OmniAuth::AuthHash.new( + uid: extern_uid, + provider: 'atlassian_oauth2', + info: { name: 'John', email: 'john@mail.com' }, + credentials: credentials + ) + end + + let(:credentials) do + { + token: SecureRandom.alphanumeric(1254), + refresh_token: SecureRandom.alphanumeric(45), + expires_at: 2.weeks.from_now.to_i, + expires: true + } + end + + subject { described_class.new(user, oauth) } + + context 'linked identity exists' do + let!(:identity) { create(:atlassian_identity, user: user, extern_uid: extern_uid) } + + before do + subject.link + end + + it 'sets #changed? to false' do + expect(subject).not_to be_changed + end + + it 'does not mark as failed' do + expect(subject).not_to be_failed + end + end + + context 'identity already linked to different user' do + let!(:identity) { create(:atlassian_identity, extern_uid: extern_uid) } + + it 'sets #changed? to false' do + subject.link + + expect(subject).not_to be_changed + end + + it 'exposes error message' do + expect(subject.error_message).to eq 'Extern uid has already been taken' + end + end + + context 'identity needs to be created' do + let(:identity) { user.atlassian_identity } + + before do + subject.link + end + + it_behaves_like 'an atlassian identity' + + it 'sets #changed? to true' do + expect(subject).to be_changed + end + end +end diff --git a/spec/lib/gitlab/auth/atlassian/user_spec.rb b/spec/lib/gitlab/auth/atlassian/user_spec.rb new file mode 100644 index 00000000000..1db01102bc2 --- /dev/null +++ b/spec/lib/gitlab/auth/atlassian/user_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Auth::Atlassian::User do + let(:oauth_user) { described_class.new(oauth) } + let(:gl_user) { oauth_user.gl_user } + let(:extern_uid) { generate(:username) } + let(:oauth) do + OmniAuth::AuthHash.new( + uid: extern_uid, + provider: 'atlassian_oauth2', + info: { name: 'John', email: 'john@mail.com' }, + credentials: credentials) + end + + let(:credentials) do + { + token: SecureRandom.alphanumeric(1254), + refresh_token: SecureRandom.alphanumeric(45), + expires_at: 2.weeks.from_now.to_i, + expires: true + } + end + + describe '.assign_identity_from_auth_hash!' do + let(:auth_hash) { ::Gitlab::Auth::Atlassian::AuthHash.new(oauth) } + let(:identity) { described_class.assign_identity_from_auth_hash!(Atlassian::Identity.new, auth_hash) } + + it_behaves_like 'an atlassian identity' + end + + describe '#save' do + context 'for an existing user' do + context 'with an existing Atlassian Identity' do + let!(:existing_user) { create(:atlassian_user, extern_uid: extern_uid) } + let(:identity) { gl_user.atlassian_identity } + + before do + oauth_user.save # rubocop:disable Rails/SaveBang + end + + it 'finds the existing user and identity' do + expect(gl_user.id).to eq(existing_user.id) + expect(identity.id).to eq(existing_user.atlassian_identity.id) + end + + it_behaves_like 'an atlassian identity' + end + + context 'for a new user' do + it 'creates the user and identity' do + oauth_user.save # rubocop:disable Rails/SaveBang + + expect(gl_user).to be_valid + end + end + end + end +end diff --git a/spec/lib/gitlab/pages/settings_spec.rb b/spec/lib/gitlab/pages/settings_spec.rb index 7d4db073d73..e495c004647 100644 --- a/spec/lib/gitlab/pages/settings_spec.rb +++ b/spec/lib/gitlab/pages/settings_spec.rb @@ -10,12 +10,6 @@ RSpec.describe Gitlab::Pages::Settings do it { is_expected.to eq('the path') } - it 'does not track calls' do - expect(::Gitlab::ErrorTracking).not_to receive(:track_exception) - - subject - end - context 'when running under a web server' do before do allow(::Gitlab::Runtime).to receive(:web_server?).and_return(true) @@ -23,24 +17,13 @@ RSpec.describe Gitlab::Pages::Settings do it { is_expected.to eq('the path') } - it 'does not track calls' do - expect(::Gitlab::ErrorTracking).not_to receive(:track_exception) - - subject - end - context 'with the env var' do before do stub_env('GITLAB_PAGES_DENY_DISK_ACCESS', '1') end - it { is_expected.to eq('the path') } - - it 'tracks a DiskAccessDenied exception' do - expect(::Gitlab::ErrorTracking).to receive(:track_exception) - .with(instance_of(described_class::DiskAccessDenied)).and_call_original - - subject + it 'raises a DiskAccessDenied exception' do + expect { subject }.to raise_error(described_class::DiskAccessDenied) end end end diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb index 5ff07dcec4f..d2b41ee31d9 100644 --- a/spec/lib/gitlab/project_authorizations_spec.rb +++ b/spec/lib/gitlab/project_authorizations_spec.rb @@ -115,6 +115,66 @@ RSpec.describe Gitlab::ProjectAuthorizations do end end + context 'user with minimal access to group' do + let_it_be(:group) { create(:group) } + let_it_be(:user) { create(:user) } + + subject(:mapping) { map_access_levels(authorizations) } + + context 'group membership' do + let!(:group_project) { create(:project, namespace: group) } + + before do + create(:group_member, :minimal_access, user: user, source: group) + end + + it 'does not create authorization' do + expect(mapping[group_project.id]).to be_nil + end + end + + context 'inherited group membership' do + let!(:sub_group) { create(:group, parent: group) } + let!(:sub_group_project) { create(:project, namespace: sub_group) } + + before do + create(:group_member, :minimal_access, user: user, source: group) + end + + it 'does not create authorization' do + expect(mapping[sub_group_project.id]).to be_nil + end + end + + context 'shared group' do + let!(:shared_group) { create(:group) } + let!(:shared_group_project) { create(:project, namespace: shared_group) } + + before do + create(:group_group_link, shared_group: shared_group, shared_with_group: group) + create(:group_member, :minimal_access, user: user, source: group) + end + + it 'does not create authorization' do + expect(mapping[shared_group_project.id]).to be_nil + end + end + + context 'shared project' do + let!(:another_group) { create(:group) } + let!(:shared_project) { create(:project, namespace: another_group) } + + before do + create(:project_group_link, group: group, project: shared_project) + create(:group_member, :minimal_access, user: user, source: group) + end + + it 'does not create authorization' do + expect(mapping[shared_project.id]).to be_nil + end + end + end + context 'with nested groups' do let(:group) { create(:group) } let!(:nested_group) { create(:group, parent: group) } diff --git a/spec/lib/gitlab/static_site_editor/config/combined_config_spec.rb b/spec/lib/gitlab/static_site_editor/config/combined_config_spec.rb new file mode 100644 index 00000000000..9a31728648b --- /dev/null +++ b/spec/lib/gitlab/static_site_editor/config/combined_config_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::StaticSiteEditor::Config::CombinedConfig do + subject(:config) { described_class.new(repository, ref, path, return_url) } + + let(:repository) { double(:repository) } + let(:ref) { double(:ref) } + let(:path) { double(:path) } + let(:return_url) { double(:return_url) } + let(:generated_data) { { generated: true } } + let(:file_data) { { file: true } } + + describe '#data' do + subject { config.data } + + before do + allow_next_instance_of(Gitlab::StaticSiteEditor::Config::GeneratedConfig) do |config| + allow(config).to receive(:data) { generated_data } + end + allow_next_instance_of(Gitlab::StaticSiteEditor::Config::FileConfig) do |config| + allow(config).to receive(:data) { file_data } + end + end + + it 'returns merged generated data and config file data' do + is_expected.to eq({ generated: true, file: true }) + end + + it 'raises an exception if any keys would be overwritten by the merge' do + generated_data[:duplicate_key] = true + file_data[:duplicate_key] = true + expect { subject }.to raise_error(StandardError, /duplicate key.*duplicate_key.*found/i) + end + end +end diff --git a/spec/lib/gitlab/static_site_editor/config/file_config_spec.rb b/spec/lib/gitlab/static_site_editor/config/file_config_spec.rb new file mode 100644 index 00000000000..665e2651d65 --- /dev/null +++ b/spec/lib/gitlab/static_site_editor/config/file_config_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::StaticSiteEditor::Config::FileConfig do + subject(:config) { described_class.new } + + describe '#data' do + subject { config.data } + + it 'returns hardcoded data for now' do + is_expected.to match( + merge_requests_illustration_path: %r{illustrations/merge_requests} + ) + end + end +end diff --git a/spec/lib/gitlab/static_site_editor/config_spec.rb b/spec/lib/gitlab/static_site_editor/config/generated_config_spec.rb index 56cdb573785..44e0c9249f5 100644 --- a/spec/lib/gitlab/static_site_editor/config_spec.rb +++ b/spec/lib/gitlab/static_site_editor/config/generated_config_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe Gitlab::StaticSiteEditor::Config do - subject(:config) { described_class.new(repository, ref, file_path, return_url) } +RSpec.describe Gitlab::StaticSiteEditor::Config::GeneratedConfig do + subject(:config) { described_class.new(repository, ref, path, return_url) } let_it_be(:namespace) { create(:namespace, name: 'namespace') } let_it_be(:root_group) { create(:group, name: 'group') } @@ -13,11 +13,11 @@ RSpec.describe Gitlab::StaticSiteEditor::Config do let_it_be(:repository) { project.repository } let(:ref) { 'master' } - let(:file_path) { 'README.md' } + let(:path) { 'README.md' } let(:return_url) { 'http://example.com' } - describe '#payload' do - subject { config.payload } + describe '#data' do + subject { config.data } it 'returns data for the frontend component' do is_expected.to eq( @@ -49,7 +49,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config do before do repository.create_file( project.creator, - file_path, + path, '', message: 'message', branch_name: 'master' @@ -57,7 +57,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config do end context 'when feature flag is enabled' do - let(:file_path) { 'FEATURE_ON.md.erb' } + let(:path) { 'FEATURE_ON.md.erb' } before do stub_feature_flags(sse_erb_support: project) @@ -67,7 +67,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config do end context 'when feature flag is disabled' do - let(:file_path) { 'FEATURE_OFF.md.erb' } + let(:path) { 'FEATURE_OFF.md.erb' } before do stub_feature_flags(sse_erb_support: false) @@ -78,7 +78,7 @@ RSpec.describe Gitlab::StaticSiteEditor::Config do end context 'when file path is nested' do - let(:file_path) { 'lib/README.md' } + let(:path) { 'lib/README.md' } it { is_expected.to include(base_url: '/namespace/project/-/sse/master%2Flib%2FREADME.md') } end @@ -90,19 +90,19 @@ RSpec.describe Gitlab::StaticSiteEditor::Config do end context 'when file does not have a markdown extension' do - let(:file_path) { 'README.txt' } + let(:path) { 'README.txt' } it { is_expected.to include(is_supported_content: 'false') } end context 'when file does not have an extension' do - let(:file_path) { 'README' } + let(:path) { 'README' } it { is_expected.to include(is_supported_content: 'false') } end context 'when file does not exist' do - let(:file_path) { 'UNKNOWN.md' } + let(:path) { 'UNKNOWN.md' } it { is_expected.to include(is_supported_content: 'false') } end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index ff574a2e2f4..44561e2e55a 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -837,6 +837,24 @@ RSpec.describe Issuable do end end + describe '#supports_severity?' do + using RSpec::Parameterized::TableSyntax + + where(:issuable_type, :supports_severity) do + :issue | false + :incident | true + :merge_request | false + end + + with_them do + let(:issuable) { build_stubbed(issuable_type) } + + subject { issuable.supports_severity? } + + it { is_expected.to eq(supports_severity) } + end + end + describe '#incident?' do using RSpec::Parameterized::TableSyntax diff --git a/spec/models/dev_ops_score/metric_spec.rb b/spec/models/dev_ops_report/metric_spec.rb index 60001d0667d..191692f43a4 100644 --- a/spec/models/dev_ops_score/metric_spec.rb +++ b/spec/models/dev_ops_report/metric_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe DevOpsScore::Metric do - let(:conv_dev_index) { create(:dev_ops_score_metric) } +RSpec.describe DevOpsReport::Metric do + let(:conv_dev_index) { create(:dev_ops_report_metric) } describe '#percentage_score' do it 'returns stored percentage score' do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 3eb74da09e1..568d5480613 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -692,6 +692,7 @@ RSpec.describe Group do before do create(:group_member, user: user, group: group_parent, access_level: parent_group_access_level) create(:group_member, user: user, group: group, access_level: group_access_level) + create(:group_member, :minimal_access, user: create(:user), source: group) create(:group_member, user: user, group: group_child, access_level: child_group_access_level) end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index d8e70911a29..39807747cc0 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -16,7 +16,6 @@ RSpec.describe Member do it { is_expected.to validate_presence_of(:user) } it { is_expected.to validate_presence_of(:source) } - it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.all_values) } it_behaves_like 'an object with email-formated attributes', :invite_email do subject { build(:project_member) } @@ -150,6 +149,7 @@ RSpec.describe Member do accepted_request_user = create(:user).tap { |u| project.request_access(u) } @accepted_request_member = project.requesters.find_by(user_id: accepted_request_user.id).tap { |m| m.accept_request } + @member_with_minimal_access = create(:group_member, :minimal_access, source: group) end describe '.access_for_user_ids' do @@ -180,6 +180,15 @@ RSpec.describe Member do it { expect(described_class.non_invite).to include @accepted_request_member } end + describe '.non_minimal_access' do + it { expect(described_class.non_minimal_access).to include @maintainer } + it { expect(described_class.non_minimal_access).to include @invited_member } + it { expect(described_class.non_minimal_access).to include @accepted_invite_member } + it { expect(described_class.non_minimal_access).to include @requested_member } + it { expect(described_class.non_minimal_access).to include @accepted_request_member } + it { expect(described_class.non_minimal_access).not_to include @member_with_minimal_access } + end + describe '.request' do it { expect(described_class.request).not_to include @maintainer } it { expect(described_class.request).not_to include @invited_member } @@ -257,6 +266,34 @@ RSpec.describe Member do it { is_expected.not_to include @blocked_maintainer } it { is_expected.not_to include @blocked_developer } end + + describe '.active' do + subject { described_class.active.to_a } + + it { is_expected.to include @owner } + it { is_expected.to include @maintainer } + it { is_expected.to include @invited_member } + it { is_expected.to include @accepted_invite_member } + it { is_expected.not_to include @requested_member } + it { is_expected.to include @accepted_request_member } + it { is_expected.not_to include @blocked_maintainer } + it { is_expected.not_to include @blocked_developer } + it { is_expected.not_to include @member_with_minimal_access } + end + + describe '.active_without_invites_and_requests' do + subject { described_class.active_without_invites_and_requests.to_a } + + it { is_expected.to include @owner } + it { is_expected.to include @maintainer } + it { is_expected.not_to include @invited_member } + it { is_expected.to include @accepted_invite_member } + it { is_expected.not_to include @requested_member } + it { is_expected.to include @accepted_request_member } + it { is_expected.not_to include @blocked_maintainer } + it { is_expected.not_to include @blocked_developer } + it { is_expected.not_to include @member_with_minimal_access } + end end describe "Delegate methods" do diff --git a/spec/presenters/dev_ops_score/metric_presenter_spec.rb b/spec/presenters/dev_ops_report/metric_presenter_spec.rb index 8b7b2c88578..306b5592c33 100644 --- a/spec/presenters/dev_ops_score/metric_presenter_spec.rb +++ b/spec/presenters/dev_ops_report/metric_presenter_spec.rb @@ -2,10 +2,10 @@ require 'spec_helper' -RSpec.describe DevOpsScore::MetricPresenter do +RSpec.describe DevOpsReport::MetricPresenter do subject { described_class.new(metric) } - let(:metric) { build(:dev_ops_score_metric) } + let(:metric) { build(:dev_ops_report_metric) } describe '#cards' do it 'includes instance score, leader score and percentage score' do diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index 13e371ad68a..396b01edbfa 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -134,6 +134,20 @@ RSpec.describe Admin::HealthCheckController, "routing" do end end +# admin_dev_ops_report GET /admin/dev_ops_report(.:format) admin/dev_ops_report#show +RSpec.describe Admin::DevOpsReportController, "routing" do + it "to #show" do + expect(get("/admin/dev_ops_report")).to route_to('admin/dev_ops_report#show') + end +end + +# admin_cohorts GET /admin/cohorts(.:format) admin/cohorst#index +RSpec.describe Admin::CohortsController, "routing" do + it "to #index" do + expect(get("/admin/cohorts")).to route_to('admin/cohorts#index') + end +end + RSpec.describe Admin::GroupsController, "routing" do let(:name) { 'complex.group-namegit' } diff --git a/spec/routing/instance_statistics_routing_spec.rb b/spec/routing/instance_statistics_routing_spec.rb index 14d2bbb78a0..7eec807fb0b 100644 --- a/spec/routing/instance_statistics_routing_spec.rb +++ b/spec/routing/instance_statistics_routing_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe 'Instance Statistics', 'routing' do include RSpec::Rails::RequestExampleGroup - it "routes '/-/instance_statistics' to dev ops score" do - expect(get('/-/instance_statistics')).to redirect_to('/admin/dev_ops_score') + it "routes '/-/instance_statistics' to dev ops report" do + expect(get('/-/instance_statistics')).to redirect_to('/admin/dev_ops_report') end end diff --git a/spec/services/authorized_project_update/project_create_service_spec.rb b/spec/services/authorized_project_update/project_create_service_spec.rb index 891800bfb87..a9d0b82acfb 100644 --- a/spec/services/authorized_project_update/project_create_service_spec.rb +++ b/spec/services/authorized_project_update/project_create_service_spec.rb @@ -81,6 +81,7 @@ RSpec.describe AuthorizedProjectUpdate::ProjectCreateService do before do create(:group_member, access_level: Gitlab::Access::REPORTER, group: group, user: group_user) create(:group_member, access_level: Gitlab::Access::MAINTAINER, group: shared_with_group, user: group_user) + create(:group_member, :minimal_access, source: shared_with_group, user: create(:user)) create(:group_group_link, shared_group: group, shared_with_group: shared_with_group, group_access: Gitlab::Access::DEVELOPER) @@ -97,6 +98,11 @@ RSpec.describe AuthorizedProjectUpdate::ProjectCreateService do access_level: Gitlab::Access::DEVELOPER) expect(project_authorization).to exist end + + it 'does not create project authorization for user with minimal access' do + expect { service.execute }.to( + change { ProjectAuthorization.count }.from(0).to(1)) + end end end @@ -118,6 +124,17 @@ RSpec.describe AuthorizedProjectUpdate::ProjectCreateService do end end + context 'member with minimal access' do + before do + create(:group_member, :minimal_access, user: group_user, source: group) + end + + it 'does not create project authorization' do + expect { service.execute }.not_to( + change { ProjectAuthorization.count }.from(0)) + end + end + context 'project has more user than BATCH_SIZE' do let(:batch_size) { 2 } let(:users) { create_list(:user, batch_size + 1 ) } diff --git a/spec/services/authorized_project_update/project_group_link_create_service_spec.rb b/spec/services/authorized_project_update/project_group_link_create_service_spec.rb index 961322a1a21..1fd47f78c24 100644 --- a/spec/services/authorized_project_update/project_group_link_create_service_spec.rb +++ b/spec/services/authorized_project_update/project_group_link_create_service_spec.rb @@ -112,6 +112,17 @@ RSpec.describe AuthorizedProjectUpdate::ProjectGroupLinkCreateService do end end + context 'minimal access member' do + before do + create(:group_member, :minimal_access, user: group_user, source: group) + end + + it 'does not create project authorization' do + expect { service.execute }.not_to( + change { ProjectAuthorization.count }.from(0)) + end + end + context 'project has more users than BATCH_SIZE' do let(:batch_size) { 2 } let(:users) { create_list(:user, batch_size + 1 ) } diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb index 401d1ed5adc..2082a163b29 100644 --- a/spec/services/submit_usage_ping_service_spec.rb +++ b/spec/services/submit_usage_ping_service_spec.rb @@ -71,12 +71,12 @@ RSpec.describe SubmitUsagePingService do shared_examples 'saves DevOps report data from the response' do it do expect { subject.execute } - .to change { DevOpsScore::Metric.count } + .to change { DevOpsReport::Metric.count } .by(1) - expect(DevOpsScore::Metric.last.leader_issues).to eq 10.2 - expect(DevOpsScore::Metric.last.instance_issues).to eq 3.2 - expect(DevOpsScore::Metric.last.percentage_issues).to eq 31.37 + expect(DevOpsReport::Metric.last.leader_issues).to eq 10.2 + expect(DevOpsReport::Metric.last.instance_issues).to eq 3.2 + expect(DevOpsReport::Metric.last.percentage_issues).to eq 31.37 end end diff --git a/spec/support/shared_examples/lib/gitlab/auth/atlassian_identity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/auth/atlassian_identity_shared_examples.rb new file mode 100644 index 00000000000..18a5087da3b --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/auth/atlassian_identity_shared_examples.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'an atlassian identity' do + it 'sets the proper values' do + expect(identity.extern_uid).to eq(extern_uid) + expect(identity.token).to eq(credentials[:token]) + expect(identity.refresh_token).to eq(credentials[:refresh_token]) + expect(identity.expires_at.to_i).to eq(credentials[:expires_at]) + end +end diff --git a/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb index 285b35fefca..777dc0c8571 100644 --- a/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'layouts/nav/sidebar/_admin' do context 'on analytics' do before do - allow(controller).to receive(:controller_name).and_return('dev_ops_score') + allow(controller).to receive(:controller_name).and_return('dev_ops_report') end it_behaves_like 'page has active tab', 'Analytics' |