Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-02-05 00:09:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-05 00:09:06 +0300
commitf4726e9f5029931fc74aee9d5eff93d6a762dcff (patch)
treebc6d47ea3d39afdf46c5df3d8328f3f266c38ae5 /app
parent7c221ba5ce130ca50b892e6dd32783e1327718df (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue39
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue13
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue8
-rw-r--r--app/assets/javascripts/alerts_settings/components/mocks/gitlabFields.json123
-rw-r--r--app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json26
-rw-r--r--app/assets/javascripts/alerts_settings/index.js10
-rw-r--r--app/assets/javascripts/alerts_settings/utils/mapping_transformations.js8
-rw-r--r--app/assets/javascripts/members/components/avatars/user_avatar.vue5
-rw-r--r--app/assets/javascripts/milestone_select.js19
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/stage.vue136
-rw-r--r--app/assets/stylesheets/page_bundles/pipelines.scss11
-rw-r--r--app/assets/stylesheets/page_bundles/signup.scss4
-rw-r--r--app/controllers/concerns/service_params.rb1
-rw-r--r--app/controllers/projects/commit_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/projects/pipelines_controller.rb1
-rw-r--r--app/controllers/projects/security/configuration_controller.rb23
-rw-r--r--app/graphql/mutations/alert_management/base.rb2
-rw-r--r--app/graphql/mutations/todos/create.rb2
-rw-r--r--app/graphql/mutations/todos/mark_all_done.rb6
-rw-r--r--app/graphql/mutations/todos/mark_done.rb4
-rw-r--r--app/graphql/mutations/todos/restore.rb4
-rw-r--r--app/graphql/mutations/todos/restore_many.rb8
-rw-r--r--app/graphql/types/alert_management/alert_type.rb2
-rw-r--r--app/graphql/types/current_user_todos.rb4
-rw-r--r--app/graphql/types/todo_type.rb18
-rw-r--r--app/graphql/types/user_type.rb2
-rw-r--r--app/helpers/projects_helper.rb31
-rw-r--r--app/helpers/users_helper.rb21
-rw-r--r--app/models/concerns/triggerable_hooks.rb3
-rw-r--r--app/models/project_services/chat_notification_service.rb26
-rw-r--r--app/models/u2f_registration.rb16
-rw-r--r--app/policies/project_policy.rb4
-rw-r--r--app/services/resource_events/base_change_timebox_service.rb7
-rw-r--r--app/services/resource_events/change_milestone_service.rb4
-rw-r--r--app/views/admin/users/_users.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_project_security_link.html.haml21
-rw-r--r--app/views/layouts/welcome.html.haml2
-rw-r--r--app/views/projects/security/configuration/show.html.haml4
-rw-r--r--app/views/registrations/welcome/show.html.haml1
-rw-r--r--app/views/shared/web_hooks/_form.html.haml1
41 files changed, 371 insertions, 259 deletions
diff --git a/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue b/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue
index 02b1d08f9c3..66d6af6f0a4 100644
--- a/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue
+++ b/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue
@@ -8,17 +8,14 @@ import {
GlSearchBoxByType,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
+import { cloneDeep } from 'lodash';
import { s__, __ } from '~/locale';
-// Mocks will be removed when integrating with BE is ready
-// data format is defined and will be the same as mocked (maybe with some minor changes)
-// feature rollout plan - https://gitlab.com/gitlab-org/gitlab/-/issues/262707#note_442529171
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import {
getMappingData,
getPayloadFields,
transformForSave,
} from '../utils/mapping_transformations';
-import gitlabFieldsMock from './mocks/gitlabFields.json';
export const i18n = {
columns: {
@@ -46,12 +43,19 @@ export default {
directives: {
GlTooltip,
},
- inject: {
- gitlabAlertFields: {
- default: gitlabFieldsMock,
- },
- },
props: {
+ alertFields: {
+ type: Array,
+ required: true,
+ validator: (fields) => {
+ return (
+ fields.length &&
+ fields.every(({ name, types, label }) => {
+ return typeof name === 'string' && Array.isArray(types) && typeof label === 'string';
+ })
+ );
+ },
+ },
parsedPayload: {
type: Array,
required: false,
@@ -65,7 +69,7 @@ export default {
},
data() {
return {
- gitlabFields: this.gitlabAlertFields,
+ gitlabFields: cloneDeep(this.alertFields),
};
},
computed: {
@@ -75,6 +79,9 @@ export default {
mappingData() {
return getMappingData(this.gitlabFields, this.payloadFields, this.savedMapping);
},
+ hasFallbackColumn() {
+ return this.gitlabFields.some(({ numberOfFallbacks }) => Boolean(numberOfFallbacks));
+ },
},
methods: {
setMapping(gitlabKey, mappingKey, valueKey) {
@@ -101,10 +108,10 @@ export default {
this.$options.i18n.makeSelection
);
},
- getFieldValue({ label, type }) {
- const types = type.map((t) => capitalizeFirstCharacter(t.toLowerCase())).join(__(' or '));
+ getFieldValue({ label, types }) {
+ const type = types.map((t) => capitalizeFirstCharacter(t.toLowerCase())).join(__(' or '));
- return `${label} (${types})`;
+ return `${label} (${type})`;
},
noResults(searchTerm, fields) {
return !this.filterFields(searchTerm, fields).length;
@@ -123,7 +130,11 @@ export default {
<h5 id="parsedFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3">
{{ $options.i18n.columns.payloadKeyTitle }}
</h5>
- <h5 id="fallbackFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3">
+ <h5
+ v-if="hasFallbackColumn"
+ id="fallbackFieldsHeader"
+ class="gl-display-table-cell gl-py-3 gl-pr-3"
+ >
{{ $options.i18n.columns.fallbackKeyTitle }}
<gl-icon
v-gl-tooltip
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
index df6d38efee7..cef20321ce2 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
@@ -125,6 +125,9 @@ export default {
prometheus: {
default: {},
},
+ multiIntegrations: {
+ default: false,
+ },
},
props: {
loading: {
@@ -135,6 +138,11 @@ export default {
type: Boolean,
required: true,
},
+ alertFields: {
+ type: Array,
+ required: false,
+ default: null,
+ },
},
apollo: {
currentIntegration: {
@@ -196,8 +204,10 @@ export default {
},
showMappingBuilder() {
return (
+ this.multiIntegrations &&
this.glFeatures.multipleHttpIntegrationsCustomMapping &&
- this.selectedIntegration === typeSet.http
+ this.selectedIntegration === typeSet.http &&
+ this.alertFields?.length
);
},
parsedSamplePayload() {
@@ -558,6 +568,7 @@ export default {
<mapping-builder
:parsed-payload="parsedSamplePayload"
:saved-mapping="savedMapping"
+ :alert-fields="alertFields"
@onMappingUpdate="updateMapping"
/>
</gl-form-group>
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
index def84f3ed94..71d094dbe6e 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
@@ -57,6 +57,13 @@ export default {
default: false,
},
},
+ props: {
+ alertFields: {
+ type: Array,
+ required: false,
+ default: null,
+ },
+ },
apollo: {
integrations: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
@@ -312,6 +319,7 @@ export default {
<alert-settings-form
:loading="isUpdating"
:can-add-integration="canAddIntegration"
+ :alert-fields="alertFields"
@create-new-integration="createNewIntegration"
@update-integration="updateIntegration"
@reset-token="resetToken"
diff --git a/app/assets/javascripts/alerts_settings/components/mocks/gitlabFields.json b/app/assets/javascripts/alerts_settings/components/mocks/gitlabFields.json
deleted file mode 100644
index e4d0e92a6f8..00000000000
--- a/app/assets/javascripts/alerts_settings/components/mocks/gitlabFields.json
+++ /dev/null
@@ -1,123 +0,0 @@
-[
- {
- "name": "TITLE",
- "label": "Title",
- "type": [
- "STRING"
- ],
- "compatibleTypes": [
- "STRING",
- "NUMBER",
- "DATETIME"
- ],
- "numberOfFallbacks": 1
- },
- {
- "name": "DESCRIPTION",
- "label": "Description",
- "type": [
- "STRING"
- ],
- "compatibleTypes": [
- "STRING",
- "NUMBER",
- "DATETIME"
- ]
- },
- {
- "name": "START_TIME",
- "label": "Start time",
- "type": [
- "DATETIME"
- ],
- "compatibleTypes": [
- "NUMBER",
- "DATETIME"
- ]
- },
- {
- "name": "END_TIME",
- "label": "End time",
- "type": [
- "DATETIME"
- ],
- "compatibleTypes": [
- "NUMBER",
- "DATETIME"
- ]
- },
- {
- "name": "SERVICE",
- "label": "Service",
- "type": [
- "STRING"
- ],
- "compatibleTypes": [
- "STRING",
- "NUMBER",
- "DATETIME"
- ]
- },
- {
- "name": "MONITORING_TOOL",
- "label": "Monitoring tool",
- "type": [
- "STRING"
- ],
- "compatibleTypes": [
- "STRING",
- "NUMBER",
- "DATETIME"
- ]
- },
- {
- "name": "HOSTS",
- "label": "Hosts",
- "type": [
- "STRING",
- "ARRAY"
- ],
- "compatibleTypes": [
- "STRING",
- "ARRAY",
- "NUMBER",
- "DATETIME"
- ]
- },
- {
- "name": "SEVERITY",
- "label": "Severity",
- "type": [
- "STRING"
- ],
- "compatibleTypes": [
- "STRING",
- "NUMBER",
- "DATETIME"
- ]
- },
- {
- "name": "FINGERPRINT",
- "label": "Fingerprint",
- "type": [
- "STRING"
- ],
- "compatibleTypes": [
- "STRING",
- "NUMBER",
- "DATETIME"
- ]
- },
- {
- "name": "GITLAB_ENVIRONMENT_NAME",
- "label": "Environment",
- "type": [
- "STRING"
- ],
- "compatibleTypes": [
- "STRING",
- "NUMBER",
- "DATETIME"
- ]
- }
-]
diff --git a/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json b/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json
index c1de0d6f0e0..80fbebf2a60 100644
--- a/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json
+++ b/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json
@@ -6,67 +6,67 @@
{
"path": ["dashboardId"],
"label": "Dashboard Id",
- "type": "STRING"
+ "type": "string"
},
{
"path": ["evalMatches"],
"label": "Eval Matches",
- "type": "ARRAY"
+ "type": "array"
},
{
"path": ["createdAt"],
"label": "Created At",
- "type": "DATETIME"
+ "type": "datetime"
},
{
"path": ["imageUrl"],
"label": "Image Url",
- "type": "STRING"
+ "type": "string"
},
{
"path": ["message"],
"label": "Message",
- "type": "STRING"
+ "type": "string"
},
{
"path": ["orgId"],
"label": "Org Id",
- "type": "STRING"
+ "type": "string"
},
{
"path": ["panelId"],
"label": "Panel Id",
- "type": "STRING"
+ "type": "string"
},
{
"path": ["ruleId"],
"label": "Rule Id",
- "type": "STRING"
+ "type": "string"
},
{
"path": ["ruleName"],
"label": "Rule Name",
- "type": "STRING"
+ "type": "string"
},
{
"path": ["ruleUrl"],
"label": "Rule Url",
- "type": "STRING"
+ "type": "string"
},
{
"path": ["state"],
"label": "State",
- "type": "STRING"
+ "type": "string"
},
{
"path": ["title"],
"label": "Title",
- "type": "STRING"
+ "type": "string"
},
{
"path": ["tags", "tag"],
"label": "Tags",
- "type": "STRING"
+ "type": "string"
}
]
}
diff --git a/app/assets/javascripts/alerts_settings/index.js b/app/assets/javascripts/alerts_settings/index.js
index 85858956987..973f5d4ec54 100644
--- a/app/assets/javascripts/alerts_settings/index.js
+++ b/app/assets/javascripts/alerts_settings/index.js
@@ -31,6 +31,7 @@ export default (el) => {
url,
projectPath,
multiIntegrations,
+ alertFields,
} = el.dataset;
return new Vue({
@@ -60,7 +61,14 @@ export default (el) => {
},
apolloProvider,
render(createElement) {
- return createElement('alert-settings-wrapper');
+ return createElement('alert-settings-wrapper', {
+ props: {
+ alertFields:
+ gon.features?.multipleHttpIntegrationsCustomMapping && parseBoolean(multiIntegrations)
+ ? JSON.parse(alertFields)
+ : null,
+ },
+ });
},
});
};
diff --git a/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js b/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js
index a7e43c93fbf..a86103540c0 100644
--- a/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js
+++ b/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js
@@ -10,9 +10,7 @@
export const getMappingData = (gitlabFields, payloadFields, savedMapping) => {
return gitlabFields.map((gitlabField) => {
// find fields from payload that match gitlab alert field by type
- const mappingFields = payloadFields.filter(({ type }) =>
- gitlabField.compatibleTypes.includes(type),
- );
+ const mappingFields = payloadFields.filter(({ type }) => gitlabField.types.includes(type));
// find the mapping that was previously stored
const foundMapping = savedMapping.find(({ fieldName }) => fieldName === gitlabField.name);
@@ -42,9 +40,9 @@ export const transformForSave = (mappingData) => {
if (mapped) {
const { path, type, label } = mapped;
acc.push({
- fieldName: field.name,
+ fieldName: field.name.toUpperCase(),
path,
- type,
+ type: type.toUpperCase(),
label,
});
}
diff --git a/app/assets/javascripts/members/components/avatars/user_avatar.vue b/app/assets/javascripts/members/components/avatars/user_avatar.vue
index ee2d5eba8e9..991f77cf3da 100644
--- a/app/assets/javascripts/members/components/avatars/user_avatar.vue
+++ b/app/assets/javascripts/members/components/avatars/user_avatar.vue
@@ -69,7 +69,10 @@ export default {
>
<template #meta>
<div v-if="statusEmoji" class="gl-p-1">
- <span v-safe-html:[$options.safeHtmlConfig]="glEmojiTag(statusEmoji)"></span>
+ <span
+ v-safe-html:[$options.safeHtmlConfig]="glEmojiTag(statusEmoji)"
+ class="user-status-emoji gl-mr-0"
+ ></span>
</div>
<div v-for="badge in badges" :key="badge.text" class="gl-p-1">
<gl-badge size="sm" :variant="badge.variant">
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 921925e15c5..badd87921d4 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -95,14 +95,19 @@ export default class MilestoneSelect {
name: m.title,
}))
.sort((mA, mB) => {
+ const dueDateA = mA.due_date ? parsePikadayDate(mA.due_date) : null;
+ const dueDateB = mB.due_date ? parsePikadayDate(mB.due_date) : null;
+
// Move all expired milestones to the bottom.
- if (mA.expired) {
- return 1;
- }
- if (mB.expired) {
- return -1;
- }
- return 0;
+ if (mA.expired) return 1;
+ if (mB.expired) return -1;
+
+ // Move milestones without due dates just above expired milestones.
+ if (!dueDateA) return 1;
+ if (!dueDateB) return -1;
+
+ // Sort by due date in ascending order.
+ return dueDateA - dueDateB;
}),
)
.then((data) => {
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
index 37d73a75c67..460aa427196 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
@@ -11,11 +11,11 @@
* 3. Merge request widget
* 4. Commit widget
*/
-
import $ from 'jquery';
-import { GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import { GlDropdown, GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import { deprecatedCreateFlash as Flash } from '~/flash';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import axios from '~/lib/utils/axios_utils';
import eventHub from '../../event_hub';
import JobItem from '../graph/job_item.vue';
@@ -24,14 +24,14 @@ import { PIPELINES_TABLE } from '../../constants';
export default {
components: {
GlIcon,
- JobItem,
GlLoadingIcon,
+ GlDropdown,
+ JobItem,
},
-
directives: {
GlTooltip: GlTooltipDirective,
},
-
+ mixins: [glFeatureFlagsMixin()],
props: {
stage: {
type: Object,
@@ -50,30 +50,25 @@ export default {
default: '',
},
},
-
data() {
return {
isLoading: false,
- dropdownContent: '',
+ dropdownContent: [],
};
},
-
computed: {
- dropdownClass() {
- return this.dropdownContent.length > 0
- ? 'js-builds-dropdown-container'
- : 'js-builds-dropdown-loading';
+ isCiMiniPipelineGlDropdown() {
+ // Feature flag ci_mini_pipeline_gl_dropdown
+ // See more at https://gitlab.com/gitlab-org/gitlab/-/issues/300400
+ return this.glFeatures?.ciMiniPipelineGlDropdown;
},
-
triggerButtonClass() {
return `ci-status-icon-${this.stage.status.group}`;
},
-
borderlessIcon() {
return `${this.stage.status.icon}_borderless`;
},
},
-
watch: {
updateDropdown() {
if (this.updateDropdown && this.isDropdownOpen() && !this.isLoading) {
@@ -81,14 +76,17 @@ export default {
}
},
},
-
updated() {
- if (this.dropdownContent.length > 0) {
+ if (!this.isCiMiniPipelineGlDropdown && this.dropdownContent.length) {
this.stopDropdownClickPropagation();
}
},
-
methods: {
+ onShowDropdown() {
+ eventHub.$emit('clickedDropdown');
+ this.isLoading = true;
+ this.fetchJobs();
+ },
onClickStage() {
if (!this.isDropdownOpen()) {
eventHub.$emit('clickedDropdown');
@@ -96,7 +94,6 @@ export default {
this.fetchJobs();
}
},
-
fetchJobs() {
axios
.get(this.stage.dropdown_path)
@@ -105,13 +102,16 @@ export default {
this.isLoading = false;
})
.catch(() => {
- this.closeDropdown();
+ if (this.isCiMiniPipelineGlDropdown) {
+ this.$refs.stageGlDropdown.hide();
+ } else {
+ this.closeDropdown();
+ }
this.isLoading = false;
Flash(__('Something went wrong on our end.'));
});
},
-
/**
* When the user right clicks or cmd/ctrl + click in the job name
* the dropdown should not be closed and the link should open in another tab,
@@ -119,6 +119,8 @@ export default {
*
* Since this component is rendered multiple times per page we need to guarantee we only
* target the click event of this component.
+ *
+ * Note: This should be removed once ci_mini_pipeline_gl_dropdown FF is removed as true.
*/
stopDropdownClickPropagation() {
$(
@@ -128,23 +130,24 @@ export default {
e.stopPropagation();
});
},
-
closeDropdown() {
if (this.isDropdownOpen()) {
$(this.$refs.dropdown).dropdown('toggle');
}
},
-
isDropdownOpen() {
return this.$el.classList.contains('show');
},
-
pipelineActionRequestComplete() {
if (this.type === PIPELINES_TABLE) {
// warn the table to update
eventHub.$emit('refreshPipelinesTable');
+ return;
+ }
+ // close the dropdown in mr widget
+ if (this.isCiMiniPipelineGlDropdown) {
+ this.$refs.stageGlDropdown.hide();
} else {
- // close the dropdown in mr widget
$(this.$refs.dropdown).dropdown('toggle');
}
},
@@ -154,32 +157,30 @@ export default {
<template>
<div class="dropdown">
- <button
- id="stageDropdown"
- ref="dropdown"
+ <gl-dropdown
+ v-if="isCiMiniPipelineGlDropdown"
+ ref="stageGlDropdown"
v-gl-tooltip.hover
- :class="triggerButtonClass"
+ data-testid="mini-pipeline-graph-dropdown"
:title="stage.title"
- class="mini-pipeline-graph-dropdown-toggle"
- data-testid="mini-pipeline-graph-dropdown-toggle"
- data-toggle="dropdown"
- data-display="static"
- type="button"
- aria-haspopup="true"
- aria-expanded="false"
- @click="onClickStage"
- >
- <span :aria-label="stage.title" aria-hidden="true" class="gl-pointer-events-none">
- <gl-icon :name="borderlessIcon" />
- </span>
- </button>
-
- <div
- class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
- aria-labelledby="stageDropdown"
+ variant="link"
+ :lazy="true"
+ :popper-opts="{ placement: 'bottom' }"
+ :toggle-class="['mini-pipeline-graph-gl-dropdown-toggle', triggerButtonClass]"
+ menu-class="mini-pipeline-graph-dropdown-menu"
+ @show="onShowDropdown"
>
+ <template #button-content>
+ <span class="gl-pointer-events-none">
+ <gl-icon :name="borderlessIcon" />
+ </span>
+ </template>
<gl-loading-icon v-if="isLoading" />
- <ul v-else class="js-builds-dropdown-list scrollable-menu">
+ <ul
+ v-else
+ class="js-builds-dropdown-list scrollable-menu"
+ data-testid="mini-pipeline-graph-dropdown-menu-list"
+ >
<li v-for="job in dropdownContent" :key="job.id">
<job-item
:dropdown-length="dropdownContent.length"
@@ -189,6 +190,45 @@ export default {
/>
</li>
</ul>
- </div>
+ </gl-dropdown>
+
+ <template v-else>
+ <button
+ id="stageDropdown"
+ ref="dropdown"
+ v-gl-tooltip.hover
+ :class="triggerButtonClass"
+ :title="stage.title"
+ class="mini-pipeline-graph-dropdown-toggle"
+ data-testid="mini-pipeline-graph-dropdown-toggle"
+ data-toggle="dropdown"
+ data-display="static"
+ type="button"
+ aria-haspopup="true"
+ aria-expanded="false"
+ @click="onClickStage"
+ >
+ <span :aria-label="stage.title" aria-hidden="true" class="gl-pointer-events-none">
+ <gl-icon :name="borderlessIcon" />
+ </span>
+ </button>
+
+ <div
+ class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
+ aria-labelledby="stageDropdown"
+ >
+ <gl-loading-icon v-if="isLoading" />
+ <ul v-else class="js-builds-dropdown-list scrollable-menu">
+ <li v-for="job in dropdownContent" :key="job.id">
+ <job-item
+ :dropdown-length="dropdownContent.length"
+ :job="job"
+ css-class-job-name="mini-pipeline-graph-dropdown-item"
+ @pipelineActionRequestComplete="pipelineActionRequestComplete"
+ />
+ </li>
+ </ul>
+ </div>
+ </template>
</div>
</template>
diff --git a/app/assets/stylesheets/page_bundles/pipelines.scss b/app/assets/stylesheets/page_bundles/pipelines.scss
index dbde7933a8b..ae36f7e3ac1 100644
--- a/app/assets/stylesheets/page_bundles/pipelines.scss
+++ b/app/assets/stylesheets/page_bundles/pipelines.scss
@@ -67,7 +67,8 @@
// Mini Pipelines
.stage-cell {
- .mini-pipeline-graph-dropdown-toggle {
+ .mini-pipeline-graph-dropdown-toggle,
+ .mini-pipeline-graph-gl-dropdown-toggle {
svg {
height: $ci-action-icon-size;
width: $ci-action-icon-size;
@@ -138,7 +139,13 @@
}
// Dropdown button in mini pipeline graph
-button.mini-pipeline-graph-dropdown-toggle {
+button.mini-pipeline-graph-dropdown-toggle,
+// As the `mini-pipeline-item` mixin specificity is lower
+// than the toggle of dropdown with 'variant="link"' we add
+// classes ".gl-button.btn-link" to make it more specific.
+// Once FF ci_mini_pipeline_gl_dropdown is removed, the `mini-pipeline-item`
+// itself could increase its specificity to simplify this selector
+button.gl-button.btn-link.mini-pipeline-graph-gl-dropdown-toggle {
@include mini-pipeline-item();
}
diff --git a/app/assets/stylesheets/page_bundles/signup.scss b/app/assets/stylesheets/page_bundles/signup.scss
index 9ed48b693b9..a207c10b04f 100644
--- a/app/assets/stylesheets/page_bundles/signup.scss
+++ b/app/assets/stylesheets/page_bundles/signup.scss
@@ -73,3 +73,7 @@
text-decoration: none;
}
}
+
+.edit-profile {
+ max-width: 460px;
+}
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index c295290a123..3cab198c1f9 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -12,6 +12,7 @@ module ServiceParams
:api_version,
:bamboo_url,
:branches_to_be_notified,
+ :labels_to_be_notified,
:build_key,
:build_type,
:ca_pem,
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 2e48f2f0e45..b694efbc1eb 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -18,6 +18,9 @@ class Projects::CommitController < Projects::ApplicationController
before_action :define_commit_vars, only: [:show, :diff_for_path, :diff_files, :pipelines, :merge_requests]
before_action :define_note_vars, only: [:show, :diff_for_path, :diff_files]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
+ before_action only: [:pipelines] do
+ push_frontend_feature_flag(:ci_mini_pipeline_gl_dropdown, @project, type: :development, default_enabled: :yaml)
+ end
BRANCH_SEARCH_LIMIT = 1000
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 14e4f3e7dd8..b8467670e4b 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -45,6 +45,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:suggestions_custom_commit, @project)
push_frontend_feature_flag(:local_file_reviews, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_notes, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:ci_mini_pipeline_gl_dropdown, @project, type: :development, default_enabled: :yaml)
record_experiment_user(:invite_members_version_a)
record_experiment_user(:invite_members_version_b)
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 68b59a3d61c..8edc2e732e0 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -16,6 +16,7 @@ class Projects::PipelinesController < Projects::ApplicationController
push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: true)
push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:graphql_pipeline_details_users, current_user, type: :development, default_enabled: :yaml)
+ push_frontend_feature_flag(:ci_mini_pipeline_gl_dropdown, project, type: :development, default_enabled: :yaml)
end
before_action :ensure_pipeline, only: [:show]
before_action :push_experiment_to_gon, only: :index, if: :html_request?
diff --git a/app/controllers/projects/security/configuration_controller.rb b/app/controllers/projects/security/configuration_controller.rb
new file mode 100644
index 00000000000..9366ca7b0ed
--- /dev/null
+++ b/app/controllers/projects/security/configuration_controller.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Projects
+ module Security
+ class ConfigurationController < Projects::ApplicationController
+ feature_category :static_application_security_testing
+
+ def show
+ return render_404 unless feature_enabled?
+
+ render_403 unless can?(current_user, :read_security_configuration, project)
+ end
+
+ private
+
+ def feature_enabled?
+ ::Feature.enabled?(:secure_security_and_compliance_configuration_page_on_ce, @project, default_enabled: :yaml)
+ end
+ end
+ end
+end
+
+Projects::Security::ConfigurationController.prepend_if_ee('EE::Projects::Security::ConfigurationController')
diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb
index 3a57cb9670d..86908c1449c 100644
--- a/app/graphql/mutations/alert_management/base.rb
+++ b/app/graphql/mutations/alert_management/base.rb
@@ -21,7 +21,7 @@ module Mutations
field :todo,
Types::TodoType,
null: true,
- description: "The todo after mutation."
+ description: "The to-do item after mutation."
field :issue,
Types::IssueType,
diff --git a/app/graphql/mutations/todos/create.rb b/app/graphql/mutations/todos/create.rb
index 814f7ec4fc4..b6250b0228c 100644
--- a/app/graphql/mutations/todos/create.rb
+++ b/app/graphql/mutations/todos/create.rb
@@ -14,7 +14,7 @@ module Mutations
field :todo, Types::TodoType,
null: true,
- description: 'The to-do created.'
+ description: 'The to-do item created.'
def resolve(target_id:)
id = ::Types::GlobalIDType[Todoable].coerce_isolated_input(target_id)
diff --git a/app/graphql/mutations/todos/mark_all_done.rb b/app/graphql/mutations/todos/mark_all_done.rb
index c8359953567..22a5893d4ec 100644
--- a/app/graphql/mutations/todos/mark_all_done.rb
+++ b/app/graphql/mutations/todos/mark_all_done.rb
@@ -10,12 +10,12 @@ module Mutations
field :updated_ids,
[::Types::GlobalIDType[::Todo]],
null: false,
- deprecated: { reason: 'Use todos', milestone: '13.2' },
- description: 'Ids of the updated todos.'
+ deprecated: { reason: 'Use to-do items', milestone: '13.2' },
+ description: 'IDs of the updated to-do items.'
field :todos, [::Types::TodoType],
null: false,
- description: 'Updated todos.'
+ description: 'Updated to-do items.'
def resolve
authorize!(current_user)
diff --git a/app/graphql/mutations/todos/mark_done.rb b/app/graphql/mutations/todos/mark_done.rb
index 1d799f11111..a78cc91da68 100644
--- a/app/graphql/mutations/todos/mark_done.rb
+++ b/app/graphql/mutations/todos/mark_done.rb
@@ -10,11 +10,11 @@ module Mutations
argument :id,
::Types::GlobalIDType[::Todo],
required: true,
- description: 'The global ID of the to-do to mark as done.'
+ description: 'The global ID of the to-do item to mark as done.'
field :todo, Types::TodoType,
null: false,
- description: 'The requested to-do.'
+ description: 'The requested to-do item.'
def resolve(id:)
todo = authorized_find!(id: id)
diff --git a/app/graphql/mutations/todos/restore.rb b/app/graphql/mutations/todos/restore.rb
index c0ac154f2a0..70c33c439c4 100644
--- a/app/graphql/mutations/todos/restore.rb
+++ b/app/graphql/mutations/todos/restore.rb
@@ -10,11 +10,11 @@ module Mutations
argument :id,
::Types::GlobalIDType[::Todo],
required: true,
- description: 'The global ID of the to-do to restore.'
+ description: 'The global ID of the to-do item to restore.'
field :todo, Types::TodoType,
null: false,
- description: 'The requested to-do.'
+ description: 'The requested to-do item.'
def resolve(id:)
todo = authorized_find!(id: id)
diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb
index ed02c054293..dc02ffadada 100644
--- a/app/graphql/mutations/todos/restore_many.rb
+++ b/app/graphql/mutations/todos/restore_many.rb
@@ -10,16 +10,16 @@ module Mutations
argument :ids,
[::Types::GlobalIDType[::Todo]],
required: true,
- description: 'The global IDs of the to-dos to restore (a maximum of 50 is supported at once).'
+ description: 'The global IDs of the to-do items to restore (a maximum of 50 is supported at once).'
field :updated_ids, [::Types::GlobalIDType[Todo]],
null: false,
description: 'The IDs of the updated to-do items.',
- deprecated: { reason: 'Use todos', milestone: '13.2' }
+ deprecated: { reason: 'Use to-do items', milestone: '13.2' }
field :todos, [::Types::TodoType],
null: false,
- description: 'Updated to-dos.'
+ description: 'Updated to-do items.'
def resolve(ids:)
check_update_amount_limit!(ids)
@@ -46,7 +46,7 @@ module Mutations
end
def raise_too_many_todos_requested_error
- raise Gitlab::Graphql::Errors::ArgumentError, 'Too many to-dos requested.'
+ raise Gitlab::Graphql::Errors::ArgumentError, 'Too many to-do items requested.'
end
def check_update_amount_limit!(ids)
diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb
index 180afd62299..6b7e7030c1f 100644
--- a/app/graphql/types/alert_management/alert_type.rb
+++ b/app/graphql/types/alert_management/alert_type.rb
@@ -112,7 +112,7 @@ module Types
field :todos,
Types::TodoType.connection_type,
null: true,
- description: 'To-dos of the current user for the alert.',
+ description: 'To-do items of the current user for the alert.',
resolver: Resolvers::TodoResolver
field :details_url,
diff --git a/app/graphql/types/current_user_todos.rb b/app/graphql/types/current_user_todos.rb
index d729f5a89bd..79a430af1d7 100644
--- a/app/graphql/types/current_user_todos.rb
+++ b/app/graphql/types/current_user_todos.rb
@@ -8,10 +8,10 @@ module Types
field_class Types::BaseField
field :current_user_todos, Types::TodoType.connection_type,
- description: 'Todos for the current user.',
+ description: 'To-do items for the current user.',
null: false do
argument :state, Types::TodoStateEnum,
- description: 'State of the todos.',
+ description: 'State of the to-do items.',
required: false
end
diff --git a/app/graphql/types/todo_type.rb b/app/graphql/types/todo_type.rb
index 919bff802c2..4cf2dbcab9e 100644
--- a/app/graphql/types/todo_type.rb
+++ b/app/graphql/types/todo_type.rb
@@ -10,42 +10,42 @@ module Types
authorize :read_todo
field :id, GraphQL::ID_TYPE,
- description: 'ID of the to-do',
+ description: 'ID of the to-do item',
null: false
field :project, Types::ProjectType,
- description: 'The project this to-do is associated with',
+ description: 'The project this to-do item is associated with',
null: true,
authorize: :read_project
field :group, Types::GroupType,
- description: 'Group this to-do is associated with',
+ description: 'Group this to-do item is associated with',
null: true,
authorize: :read_group
field :author, Types::UserType,
- description: 'The author of this to-do',
+ description: 'The author of this to-do item',
null: false
field :action, Types::TodoActionEnum,
- description: 'Action of the to-do',
+ description: 'Action of the to-do item',
null: false
field :target_type, Types::TodoTargetEnum,
- description: 'Target type of the to-do',
+ description: 'Target type of the to-do item',
null: false
field :body, GraphQL::STRING_TYPE,
- description: 'Body of the to-do',
+ description: 'Body of the to-do item',
null: false,
calls_gitaly: true # TODO This is only true when `target_type` is `Commit`. See https://gitlab.com/gitlab-org/gitlab/issues/34757#note_234752665
field :state, Types::TodoStateEnum,
- description: 'State of the to-do',
+ description: 'State of the to-do item',
null: false
field :created_at, Types::TimeType,
- description: 'Timestamp this to-do was created',
+ description: 'Timestamp this to-do item was created',
null: false
def project
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index 93503268319..c179c84ba84 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -31,7 +31,7 @@ module Types
description: 'Web path of the user'
field :todos, Types::TodoType.connection_type, null: false,
resolver: Resolvers::TodoResolver,
- description: 'Todos of the user'
+ description: 'To-do items of the user'
field :group_memberships, Types::GroupMemberType.connection_type, null: true,
description: 'Group memberships of the user'
field :group_count, GraphQL::INT_TYPE, null: true,
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index e2fd6305892..a2e9952f350 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -139,6 +139,10 @@ module ProjectsHelper
project_nav_tabs.include? name
end
+ def any_project_nav_tab?(tabs)
+ tabs.any? { |tab| project_nav_tab?(tab) }
+ end
+
def project_for_deploy_key(deploy_key)
if deploy_key.has_access_to?(@project)
@project
@@ -374,6 +378,20 @@ module ProjectsHelper
private
+ def can_read_security_configuration?(project, current_user)
+ ::Feature.enabled?(:secure_security_and_compliance_configuration_page_on_ce, @subject, default_enabled: :yaml) &&
+ can?(current_user, :read_security_configuration, project)
+ end
+
+ def get_project_security_nav_tabs(project, current_user)
+ if can_read_security_configuration?(project, current_user)
+ [:security_and_compliance, :security_configuration]
+ else
+ []
+ end
+ end
+
+ # rubocop:disable Metrics/CyclomaticComplexity
def get_project_nav_tabs(project, current_user)
nav_tabs = [:home]
@@ -382,6 +400,8 @@ module ProjectsHelper
nav_tabs << :releases if can?(current_user, :read_release, project)
end
+ nav_tabs += get_project_security_nav_tabs(project, current_user)
+
if project.repo_exists? && can?(current_user, :read_merge_request, project)
nav_tabs << :merge_requests
end
@@ -415,6 +435,7 @@ module ProjectsHelper
nav_tabs
end
+ # rubocop:enable Metrics/CyclomaticComplexity
def package_nav_tabs(project, current_user)
[].tap do |tabs|
@@ -695,6 +716,12 @@ module ProjectsHelper
"#{request.path}?#{options.to_param}"
end
+ def sidebar_security_configuration_paths
+ %w[
+ projects/security/configuration#show
+ ]
+ end
+
def sidebar_projects_paths
%w[
projects#show
@@ -759,6 +786,10 @@ module ProjectsHelper
]
end
+ def sidebar_security_paths
+ %w[projects/security/configuration#show]
+ end
+
def user_can_see_auto_devops_implicitly_enabled_banner?(project, user)
Ability.allowed?(user, :admin_project, project) &&
project.has_auto_devops_implicitly_enabled? &&
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index a5d4d6872df..1ea2d4412b1 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -299,6 +299,27 @@ module UsersHelper
html_escape(s_('Profile|%{job_title} at %{organization}')) % { job_title: job_title, organization: organization }
end
+
+ def user_table_headers
+ [
+ {
+ section_class_name: 'section-40',
+ header_text: _('Name')
+ },
+ {
+ section_class_name: 'section-10',
+ header_text: _('Projects')
+ },
+ {
+ section_class_name: 'section-15',
+ header_text: _('Created on')
+ },
+ {
+ section_class_name: 'section-15',
+ header_text: _('Last activity')
+ }
+ ]
+ end
end
UsersHelper.prepend_if_ee('EE::UsersHelper')
diff --git a/app/models/concerns/triggerable_hooks.rb b/app/models/concerns/triggerable_hooks.rb
index 473b430bb04..db5df6c2c9f 100644
--- a/app/models/concerns/triggerable_hooks.rb
+++ b/app/models/concerns/triggerable_hooks.rb
@@ -16,7 +16,8 @@ module TriggerableHooks
deployment_hooks: :deployment_events,
feature_flag_hooks: :feature_flag_events,
release_hooks: :releases_events,
- member_hooks: :member_events
+ member_hooks: :member_events,
+ subgroup_hooks: :subgroup_events
}.freeze
extend ActiveSupport::Concern
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index c9e97efb4ac..1d50d5cf19e 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -11,11 +11,13 @@ class ChatNotificationService < Service
tag_push pipeline wiki_page deployment
].freeze
+ SUPPORTED_EVENTS_FOR_LABEL_FILTER = %w[issue confidential_issue merge_request note confidential_note].freeze
+
EVENT_CHANNEL = proc { |event| "#{event}_channel" }
default_value_for :category, 'chat'
- prop_accessor :webhook, :username, :channel, :branches_to_be_notified
+ prop_accessor :webhook, :username, :channel, :branches_to_be_notified, :labels_to_be_notified
# Custom serialized properties initialization
prop_accessor(*SUPPORTED_EVENTS.map { |event| EVENT_CHANNEL[event] })
@@ -62,12 +64,16 @@ class ChatNotificationService < Service
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}", required: true }.freeze,
{ type: 'text', name: 'username', placeholder: 'e.g. GitLab' }.freeze,
{ type: 'checkbox', name: 'notify_only_broken_pipelines' }.freeze,
- { type: 'select', name: 'branches_to_be_notified', choices: branch_choices }.freeze
+ { type: 'select', name: 'branches_to_be_notified', choices: branch_choices }.freeze,
+ { type: 'text', name: 'labels_to_be_notified', placeholder: 'e.g. ~backend', help: 'Only supported for issue, merge request and note events.' }.freeze
].freeze
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
+
+ return unless notify_label?(data)
+
return unless webhook.present?
object_kind = data[:object_kind]
@@ -114,6 +120,22 @@ class ChatNotificationService < Service
private
+ def labels_to_be_notified_list
+ return [] if labels_to_be_notified.nil?
+
+ labels_to_be_notified.delete('~').split(',').map(&:strip)
+ end
+
+ def notify_label?(data)
+ return true unless SUPPORTED_EVENTS_FOR_LABEL_FILTER.include?(data[:object_kind]) && labels_to_be_notified.present?
+
+ issue_labels = data.dig(:issue, :labels) || []
+ merge_request_labels = data.dig(:merge_request, :labels) || []
+ label_titles = (issue_labels + merge_request_labels).pluck(:title)
+
+ (labels_to_be_notified_list & label_titles).any?
+ end
+
# every notifier must implement this independently
def notify(message, opts)
raise NotImplementedError
diff --git a/app/models/u2f_registration.rb b/app/models/u2f_registration.rb
index 1a389081913..65dc7a47533 100644
--- a/app/models/u2f_registration.rb
+++ b/app/models/u2f_registration.rb
@@ -4,11 +4,19 @@
class U2fRegistration < ApplicationRecord
belongs_to :user
- after_commit :schedule_webauthn_migration, on: :create
- after_commit :update_webauthn_registration, on: :update, if: :counter_changed?
- def schedule_webauthn_migration
- BackgroundMigrationWorker.perform_async('MigrateU2fWebauthn', [id, id])
+ after_create :create_webauthn_registration
+ after_update :update_webauthn_registration, if: :counter_changed?
+
+ def create_webauthn_registration
+ converter = Gitlab::Auth::U2fWebauthnConverter.new(self)
+ WebauthnRegistration.create!(converter.convert)
+ rescue StandardError => ex
+ Gitlab::AppJsonLogger.error(
+ event: 'u2f_migration',
+ error: ex.class.name,
+ backtrace: ::Gitlab::BacktraceCleaner.clean_backtrace(ex.backtrace),
+ message: "U2F to WebAuthn conversion failed")
end
def update_webauthn_registration
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index f6d1b376b92..83acf0c12d7 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -581,6 +581,10 @@ class ProjectPolicy < BasePolicy
enable :read_issue_link
end
+ rule { can?(:developer_access) }.policy do
+ enable :read_security_configuration
+ end
+
# Design abilities could also be prevented in the issue policy.
rule { design_management_disabled }.policy do
prevent :read_design
diff --git a/app/services/resource_events/base_change_timebox_service.rb b/app/services/resource_events/base_change_timebox_service.rb
index 5c83f7b12f7..d802bbee107 100644
--- a/app/services/resource_events/base_change_timebox_service.rb
+++ b/app/services/resource_events/base_change_timebox_service.rb
@@ -2,12 +2,11 @@
module ResourceEvents
class BaseChangeTimeboxService
- attr_reader :resource, :user, :event_created_at
+ attr_reader :resource, :user
- def initialize(resource, user, created_at: Time.current)
+ def initialize(resource, user)
@resource = resource
@user = user
- @event_created_at = created_at
end
def execute
@@ -27,7 +26,7 @@ module ResourceEvents
{
user_id: user.id,
- created_at: event_created_at,
+ created_at: resource.system_note_timestamp,
key => resource.id
}
end
diff --git a/app/services/resource_events/change_milestone_service.rb b/app/services/resource_events/change_milestone_service.rb
index dcdf87599ac..24935a3327a 100644
--- a/app/services/resource_events/change_milestone_service.rb
+++ b/app/services/resource_events/change_milestone_service.rb
@@ -4,8 +4,8 @@ module ResourceEvents
class ChangeMilestoneService < BaseChangeTimeboxService
attr_reader :milestone, :old_milestone
- def initialize(resource, user, created_at: Time.current, old_milestone:)
- super(resource, user, created_at: created_at)
+ def initialize(resource, user, old_milestone:)
+ super(resource, user)
@milestone = resource&.milestone
@old_milestone = old_milestone
diff --git a/app/views/admin/users/_users.html.haml b/app/views/admin/users/_users.html.haml
index 697c0175b4f..57edb9abe90 100644
--- a/app/views/admin/users/_users.html.haml
+++ b/app/views/admin/users/_users.html.haml
@@ -78,10 +78,8 @@
- else
.table-holder
.thead-white.text-nowrap.gl-responsive-table-row.table-row-header{ role: 'row' }
- .table-section.section-40{ role: 'rowheader' }= _('Name')
- .table-section.section-10{ role: 'rowheader' }= _('Projects')
- .table-section.section-15{ role: 'rowheader' }= _('Created on')
- .table-section.section-15{ role: 'rowheader' }= _('Last activity')
+ - user_table_headers.each do |header|
+ .table-section{ class: header[:section_class_name], role: 'rowheader' }= header[:header_text]
= render partial: 'admin/users/user', collection: @users
diff --git a/app/views/layouts/nav/sidebar/_project_security_link.html.haml b/app/views/layouts/nav/sidebar/_project_security_link.html.haml
new file mode 100644
index 00000000000..426845639e3
--- /dev/null
+++ b/app/views/layouts/nav/sidebar/_project_security_link.html.haml
@@ -0,0 +1,21 @@
+- top_level_link = project_security_configuration_path(@project)
+- top_level_qa_selector = 'security_configuration_link'
+- if any_project_nav_tab?([:security_configuration])
+ = nav_link(path: sidebar_security_paths) do
+ = link_to top_level_link, data: { qa_selector: top_level_qa_selector } do
+ .nav-icon-container
+ = sprite_icon('shield')
+ %span.nav-item-name
+ = _('Security & Compliance')
+
+ %ul.sidebar-sub-level-items
+ = nav_link(path: sidebar_security_paths, html_options: { class: "fly-out-top-item" } ) do
+ = link_to top_level_link do
+ %strong.fly-out-top-item-name
+ = _('Security & Compliance')
+
+ %li.divider.fly-out-top-item
+ - if project_nav_tab?(:security_configuration)
+ = nav_link(path: sidebar_security_configuration_paths) do
+ = link_to project_security_configuration_path(@project), title: _('Configuration'), data: { qa_selector: 'security_configuration_link'} do
+ %span= _('Configuration')
diff --git a/app/views/layouts/welcome.html.haml b/app/views/layouts/welcome.html.haml
index 30ba7f7f230..944f524d692 100644
--- a/app/views/layouts/welcome.html.haml
+++ b/app/views/layouts/welcome.html.haml
@@ -4,5 +4,5 @@
%body.ui-indigo.gl-display-flex.vh-100
= render "layouts/header/logo_with_title"
= render "layouts/broadcast"
- .container.d-flex.flex-grow-1.m-0
+ .container.gl-display-flex.gl-flex-grow-1
= yield
diff --git a/app/views/projects/security/configuration/show.html.haml b/app/views/projects/security/configuration/show.html.haml
new file mode 100644
index 00000000000..1a371955be8
--- /dev/null
+++ b/app/views/projects/security/configuration/show.html.haml
@@ -0,0 +1,4 @@
+- breadcrumb_title _("Security Configuration")
+- page_title _("Security Configuration")
+
+#js-security-configuration-static
diff --git a/app/views/registrations/welcome/show.html.haml b/app/views/registrations/welcome/show.html.haml
index d44a29a5bba..d2a2853ecd7 100644
--- a/app/views/registrations/welcome/show.html.haml
+++ b/app/views/registrations/welcome/show.html.haml
@@ -1,4 +1,5 @@
- page_title _('Your profile')
+- add_page_specific_style 'page_bundles/signup'
.row.gl-flex-grow-1
.d-flex.gl-flex-direction-column.gl-align-items-center.gl-w-full.gl-p-5
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index 667b56c725a..f3d9b9cfe27 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -50,6 +50,7 @@
= s_('Webhooks|This URL will be triggered when a confidential issue is created/updated/merged')
- if @group
= render_if_exists 'groups/hooks/member_events', form: form
+ = render_if_exists 'groups/hooks/subgroup_events', form: form
%li
= form.check_box :merge_requests_events, class: 'form-check-input'
= form.label :merge_requests_events, class: 'list-label form-check-label gl-ml-1' do