Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-10 18:08:54 +00:00
parent 9afe9ca576
commit c596046be9
137 changed files with 2086 additions and 401 deletions

View File

@ -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

View File

@ -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'

View File

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,12 @@
query getHighlightBarInfo($iid: String!, $fullPath: ID!) {
project(fullPath: $fullPath) {
issue(iid: $iid) {
alertManagementAlert {
title
detailsUrl
createdAt
eventCount
}
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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: {

View File

@ -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

View File

@ -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>
</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 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>
</smart-virtual-list>
<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>
</div>
</div>
<div v-else>

View File

@ -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-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>
<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>
<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-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>
</smart-virtual-list>
<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>
<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>
<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>
</div>
</div>
<div v-else>

View File

@ -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}'),
};

View File

@ -0,0 +1,9 @@
mutation updateIssuableSeverity($projectPath: ID!, $severity: IssuableSeverity!, $iid: String!) {
issueSetSeverity(input: { iid: $iid, severity: $severity, projectPath: $projectPath }) {
errors
issue {
iid
severity
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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 };

View File

@ -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';

View File

@ -1,3 +1,4 @@
.incident-severity,
.incident-management-list,
.alert-management-details {
.icon-critical {

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module DevOpsScoreHelper
module DevOpsReportHelper
def score_level(score)
if score < 33.33
'low'

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -185,6 +185,10 @@ module Issuable
is_a?(TimeTrackable) && !incident?
end
def supports_severity?
incident?
end
def incident?
is_a?(Issue) && super
end

View File

@ -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

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module DevOpsScore
module DevOpsReport
class Card
attr_accessor :metric, :title, :description, :feature, :blog, :docs

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module DevOpsScore
module DevOpsReport
class IdeaToProductionStep
attr_accessor :metric, :title, :features

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module DevOpsScore
module DevOpsReport
class Metric < ApplicationRecord
include Presentable

View File

@ -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)

View File

@ -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) }

View File

@ -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) }

View File

@ -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

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module DevOpsScore
module DevOpsReport
class MetricPresenter < Gitlab::View::Presenter::Simple
def cards
[

View File

@ -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

View File

@ -3,6 +3,7 @@
class IssueSidebarBasicEntity < IssuableSidebarBasicEntity
expose :due_date
expose :confidential
expose :severity
end
IssueSidebarBasicEntity.prepend_if_ee('EE::IssueSidebarBasicEntity')

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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.')

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -1 +1 @@
#static-site-editor{ data: @config.payload.merge({ merge_requests_illustration_path: image_path('illustrations/merge_requests.svg') }) }
#static-site-editor{ data: @data }

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Remove virtual scroll list from pipeline test report
merge_request: 41935
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Incident severity widget
merge_request: 39859
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Incident highlight bar widget
merge_request: 41702
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add OmniAuth sign-in via Atlassian Cloud
merge_request: 40178
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Externalize i18n strings from admin dashboard
merge_request: 41387
author: Takuya Noguchi
type: other

View File

@ -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:

View File

@ -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'

View File

@ -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',

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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:**

View File

@ -13088,6 +13088,41 @@ type Project {
state: [VulnerabilityState!]
): 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
"""

View File

@ -38171,6 +38171,87 @@
"isDeprecated": false,
"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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
---

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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|Youre 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 ""

View File

@ -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 = {

View File

@ -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}"

View File

@ -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!

View File

@ -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'

View File

@ -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

View File

@ -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) }

View File

@ -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 }

Some files were not shown because too many files have changed in this diff Show More