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>2023-08-09 12:10:54 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-08-09 12:10:54 +0300
commitc2de38f36d2fb75a17ce161fa69f2b8a5e670f3e (patch)
treed24a576c60f21055b8e2bc7f0d9954830d8a229c /app
parent40338034578aca9d622651a060cbf157a941361b (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/comment_templates/components/form.vue3
-rw-r--r--app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue24
-rw-r--r--app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue120
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue46
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue2
-rw-r--r--app/controllers/projects/environments_controller.rb7
-rw-r--r--app/helpers/mirror_helper.rb5
-rw-r--r--app/models/application_setting.rb3
-rw-r--r--app/models/merge_request.rb33
-rw-r--r--app/validators/json_schemas/application_setting_prometheus_alert_db_indicators_settings.json (renamed from app/validators/json_schemas/application_setting_database_apdex_settings.json)2
-rw-r--r--app/views/projects/merge_requests/creations/new.html.haml6
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml65
-rw-r--r--app/views/projects/mirrors/_mirror_repos_list.html.haml86
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml2
14 files changed, 238 insertions, 166 deletions
diff --git a/app/assets/javascripts/comment_templates/components/form.vue b/app/assets/javascripts/comment_templates/components/form.vue
index 334c67ca339..0dadec7a5c5 100644
--- a/app/assets/javascripts/comment_templates/components/form.vue
+++ b/app/assets/javascripts/comment_templates/components/form.vue
@@ -5,6 +5,7 @@ import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import { logError } from '~/lib/logger';
import { __ } from '~/locale';
+import { InternalEvents } from '~/tracking';
import createSavedReplyMutation from '../queries/create_saved_reply.mutation.graphql';
import updateSavedReplyMutation from '../queries/update_saved_reply.mutation.graphql';
@@ -17,6 +18,7 @@ export default {
GlAlert,
MarkdownField,
},
+ mixins: [InternalEvents.mixin()],
props: {
id: {
type: String,
@@ -91,6 +93,7 @@ export default {
this.$emit('saved');
this.updateCommentTemplate = { name: '', content: '' };
this.showValidation = false;
+ this.track_event('i_code_review_saved_replies_create');
}
},
})
diff --git a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue
index b13b0ede9f0..e1c47fde81d 100644
--- a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue
+++ b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue
@@ -24,10 +24,10 @@ export default {
static: true,
lazy: true,
},
- translations: {
+ i18n: {
cronPlaceholder: '* * * * *',
cronSyntaxInstructions: __(
- 'Define a custom deploy freeze pattern with %{cronSyntaxStart}cron syntax%{cronSyntaxEnd}',
+ 'Define a custom deploy freeze pattern with %{cronSyntaxStart}cron syntax%{cronSyntaxEnd}.',
),
addTitle: __('Add deploy freeze'),
editTitle: __('Edit deploy freeze'),
@@ -81,9 +81,7 @@ export default {
return Boolean(this.selectedId);
},
modalTitle() {
- return this.isEditing
- ? this.$options.translations.editTitle
- : this.$options.translations.addTitle;
+ return this.isEditing ? this.$options.i18n.editTitle : this.$options.i18n.addTitle;
},
},
methods: {
@@ -104,6 +102,13 @@ export default {
this.addFreezePeriod();
}
},
+ focusFirstInput() {
+ if (this.$refs.freezeStartCron) {
+ setTimeout(() => {
+ this.$refs.freezeStartCron?.$el?.focus();
+ }, 250);
+ }
+ },
},
};
</script>
@@ -115,9 +120,10 @@ export default {
:action-primary="addDeployFreezeButton"
@primary="submit"
@canceled="resetModalHandler"
+ @change="focusFirstInput"
>
<p>
- <gl-sprintf :message="$options.translations.cronSyntaxInstructions">
+ <gl-sprintf :message="$options.i18n.cronSyntaxInstructions">
<template #cronSyntax="{ content }">
<gl-link href="https://crontab.guru/" target="_blank">{{ content }}</gl-link>
</template>
@@ -132,11 +138,13 @@ export default {
>
<gl-form-input
id="deploy-freeze-start"
+ ref="freezeStartCron"
v-model="freezeStartCron"
class="gl-font-monospace!"
data-qa-selector="deploy_freeze_start_field"
- :placeholder="$options.translations.cronPlaceholder"
+ :placeholder="$options.i18n.cronPlaceholder"
:state="freezeStartCronState"
+ autofocus
trim
/>
</gl-form-group>
@@ -152,7 +160,7 @@ export default {
v-model="freezeEndCron"
class="gl-font-monospace!"
data-qa-selector="deploy_freeze_end_field"
- :placeholder="$options.translations.cronPlaceholder"
+ :placeholder="$options.i18n.cronPlaceholder"
:state="freezeEndCronState"
trim
/>
diff --git a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue
index 77767456f76..24ea8f01c20 100644
--- a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue
+++ b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue
@@ -1,39 +1,48 @@
<script>
-import { GlTable, GlButton, GlModal, GlModalDirective, GlSprintf } from '@gitlab/ui';
+import {
+ GlCard,
+ GlTable,
+ GlButton,
+ GlIcon,
+ GlModal,
+ GlModalDirective,
+ GlSprintf,
+} from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
-import { s__ } from '~/locale';
+import { __, s__ } from '~/locale';
export default {
fields: [
{
key: 'freezeStart',
label: s__('DeployFreeze|Freeze start'),
+ tdClass: 'gl-vertical-align-middle!',
},
{
key: 'freezeEnd',
label: s__('DeployFreeze|Freeze end'),
+ tdClass: 'gl-vertical-align-middle!',
},
{
key: 'cronTimezone',
label: s__('DeployFreeze|Time zone'),
+ tdClass: 'gl-vertical-align-middle!',
},
{
- key: 'edit',
- label: s__('DeployFreeze|Edit'),
- },
- {
- key: 'delete',
- label: s__('DeployFreeze|Delete'),
+ key: 'actions',
+ label: __('Actions'),
+ thClass: 'gl-text-right',
},
],
- translations: {
+ i18n: {
+ title: s__('DeployFreeze|Deploy freezes'),
addDeployFreeze: s__('DeployFreeze|Add deploy freeze'),
deleteDeployFreezeTitle: s__('DeployFreeze|Delete deploy freeze?'),
deleteDeployFreezeMessage: s__(
'DeployFreeze|Deploy freeze from %{start} to %{end} in %{timezone} will be removed. Are you sure?',
),
emptyStateText: s__(
- 'DeployFreeze|No deploy freezes exist for this project. To add one, select %{strongStart}Add deploy freeze%{strongEnd}',
+ 'DeployFreeze|No deploy freezes exist for this project. To add one, select %{strongStart}Add deploy freeze%{strongEnd} above.',
),
},
modal: {
@@ -42,10 +51,16 @@ export default {
text: s__('DeployFreeze|Delete freeze period'),
attributes: { variant: 'danger', 'data-testid': 'modal-confirm' },
},
+ actionSecondary: {
+ text: __('Cancel'),
+ attributes: { variant: 'default' },
+ },
},
components: {
+ GlCard,
GlTable,
GlButton,
+ GlIcon,
GlModal,
GlSprintf,
},
@@ -80,65 +95,78 @@ export default {
</script>
<template>
- <div class="deploy-freeze-table">
+ <gl-card
+ class="gl-new-card deploy-freeze-table"
+ header-class="gl-new-card-header"
+ body-class="gl-new-card-body gl-px-0"
+ >
+ <template #header>
+ <div class="gl-new-card-title-wrapper">
+ <h3 class="gl-new-card-title">{{ $options.i18n.title }}</h3>
+ <span class="gl-new-card-count">
+ <gl-icon name="deployments" class="gl-mr-2" />
+ {{ freezePeriods.length }}
+ </span>
+ </div>
+ <div class="gl-new-card-actions">
+ <gl-button v-gl-modal.deploy-freeze-modal size="small" data-testid="add-deploy-freeze">{{
+ $options.i18n.addDeployFreeze
+ }}</gl-button>
+ </div>
+ </template>
+
<gl-table
data-testid="deploy-freeze-table"
:items="freezePeriods"
:fields="$options.fields"
show-empty
- stacked="lg"
+ stacked="md"
>
<template #cell(cronTimezone)="{ item }">
{{ item.cronTimezone.formattedTimezone }}
</template>
- <template #cell(edit)="{ item }">
- <gl-button
- v-gl-modal.deploy-freeze-modal
- icon="pencil"
- data-testid="edit-deploy-freeze"
- :aria-label="__('Edit deploy freeze')"
- @click="setFreezePeriod(item)"
- />
- </template>
- <template #cell(delete)="{ item }">
- <gl-button
- v-gl-modal="$options.modal.id"
- category="secondary"
- variant="danger"
- icon="remove"
- :aria-label="$options.modal.actionPrimary.text"
- :loading="item.isDeleting"
- data-testid="delete-deploy-freeze"
- @click="handleDeleteFreezePeriod(item)"
- />
+ <template #cell(actions)="{ item }">
+ <div class="gl-display-flex gl-justify-content-end gl-mt-n2 gl-mb-n2">
+ <gl-button
+ v-gl-modal.deploy-freeze-modal
+ icon="pencil"
+ data-testid="edit-deploy-freeze"
+ :aria-label="__('Edit deploy freeze')"
+ class="gl-mr-3"
+ @click="setFreezePeriod(item)"
+ />
+ <gl-button
+ v-gl-modal="$options.modal.id"
+ category="secondary"
+ variant="danger"
+ icon="remove"
+ :aria-label="$options.modal.actionPrimary.text"
+ :loading="item.isDeleting"
+ data-testid="delete-deploy-freeze"
+ @click="handleDeleteFreezePeriod(item)"
+ />
+ </div>
</template>
<template #empty>
- <p data-testid="empty-freeze-periods" class="gl-text-center text-plain">
- <gl-sprintf :message="$options.translations.emptyStateText">
+ <p data-testid="empty-freeze-periods" class="gl-text-secondary gl-text-center gl-mb-0">
+ <gl-sprintf :message="$options.i18n.emptyStateText">
<template #strong="{ content }">
- <strong>{{ content }}</strong>
+ {{ content }}
</template>
</gl-sprintf>
</p>
</template>
</gl-table>
- <gl-button
- v-gl-modal.deploy-freeze-modal
- data-testid="add-deploy-freeze"
- category="primary"
- variant="confirm"
- >
- {{ $options.translations.addDeployFreeze }}
- </gl-button>
<gl-modal
- :title="$options.translations.deleteDeployFreezeTitle"
+ :title="$options.i18n.deleteDeployFreezeTitle"
:modal-id="$options.modal.id"
:action-primary="$options.modal.actionPrimary"
+ :action-secondary="$options.modal.actionSecondary"
static
@primary="confirmDeleteFreezePeriod"
>
<template v-if="freezePeriodToDelete">
- <gl-sprintf :message="$options.translations.deleteDeployFreezeMessage">
+ <gl-sprintf :message="$options.i18n.deleteDeployFreezeMessage">
<template #start>
<code>{{ freezePeriodToDelete.freezeStart }}</code>
</template>
@@ -149,5 +177,5 @@ export default {
</gl-sprintf>
</template>
</gl-modal>
- </div>
+ </gl-card>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
index bb2ca838279..b58e2249829 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
@@ -6,6 +6,7 @@ import { fetchPolicies } from '~/lib/graphql';
import { joinPaths } from '~/lib/utils/url_utility';
import Tracking from '~/tracking';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
+import PersistedPagination from '~/packages_and_registries/shared/components/persisted_pagination.vue';
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
@@ -27,6 +28,7 @@ import {
import getContainerRepositoryTagsQuery from '../../graphql/queries/get_container_repository_tags.query.graphql';
import deleteContainerRepositoryTagsMutation from '../../graphql/mutations/delete_container_repository_tags.mutation.graphql';
import DeleteModal from '../delete_modal.vue';
+import { getPageParams, getNextPageParams, getPreviousPageParams } from '../../utils';
import TagsListRow from './tags_list_row.vue';
export default {
@@ -37,6 +39,7 @@ export default {
TagsListRow,
TagsLoader,
RegistryList,
+ PersistedPagination,
PersistedSearch,
},
mixins: [Tracking.mixin()],
@@ -62,7 +65,7 @@ export default {
required: false,
},
},
- searchConfig: { NAME_SORT_FIELD },
+ sortableFields: [NAME_SORT_FIELD],
i18n: {
REMOVE_TAGS_BUTTON_TITLE,
TAGS_LIST_TITLE,
@@ -87,7 +90,7 @@ export default {
containerRepository: {},
filters: {},
itemsToBeDeleted: [],
- mutationLoading: false,
+ isDeleteInProgress: false,
sort: null,
pageParams: {},
};
@@ -121,7 +124,7 @@ export default {
return (
this.isImageLoading ||
this.$apollo.queries.containerRepository.loading ||
- this.mutationLoading ||
+ this.isDeleteInProgress ||
!this.sort
);
},
@@ -153,7 +156,7 @@ export default {
async handleDeleteTag() {
this.track('confirm_delete');
const { itemsToBeDeleted } = this;
- this.mutationLoading = true;
+ this.isDeleteInProgress = true;
try {
const { data } = await this.$apollo.mutate({
mutation: deleteContainerRepositoryTagsMutation,
@@ -180,24 +183,17 @@ export default {
} catch (e) {
this.$emit('delete', itemsToBeDeleted.length === 1 ? ALERT_DANGER_TAG : ALERT_DANGER_TAGS);
} finally {
- this.mutationLoading = false;
+ this.isDeleteInProgress = false;
}
},
fetchNextPage() {
- this.pageParams = {
- after: this.tagsPageInfo?.endCursor,
- first: GRAPHQL_PAGE_SIZE,
- };
+ this.pageParams = getNextPageParams(this.tagsPageInfo?.endCursor);
},
fetchPreviousPage() {
- this.pageParams = {
- first: null,
- before: this.tagsPageInfo?.startCursor,
- last: GRAPHQL_PAGE_SIZE,
- };
+ this.pageParams = getPreviousPageParams(this.tagsPageInfo?.startCursor);
},
- handleSearchUpdate({ sort, filters }) {
- this.pageParams = {};
+ handleSearchUpdate({ sort, filters, pageInfo }) {
+ this.pageParams = getPageParams(pageInfo);
this.sort = sort;
const parsed = {
@@ -224,10 +220,8 @@ export default {
<div>
<persisted-search
class="gl-mb-5"
- :sortable-fields="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
- $options.searchConfig.NAME_SORT_FIELD,
- ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
- :default-order="$options.searchConfig.NAME_SORT_FIELD.orderBy"
+ :sortable-fields="$options.sortableFields"
+ :default-order="$options.sortableFields[0].orderBy"
default-sort="asc"
@update="handleSearchUpdate"
/>
@@ -244,11 +238,8 @@ export default {
<registry-list
:hidden-delete="hideBulkDelete"
:title="listTitle"
- :pagination="tagsPageInfo"
:items="tags"
id-property="name"
- @prev-page="fetchPreviousPage"
- @next-page="fetchNextPage"
@delete="deleteTags"
>
<template #default="{ selectItem, isSelected, item, first }">
@@ -272,5 +263,14 @@ export default {
/>
</template>
</template>
+
+ <div v-if="!isDeleteInProgress" class="gl-display-flex gl-justify-content-center">
+ <persisted-pagination
+ class="gl-mt-3"
+ :pagination="tagsPageInfo"
+ @prev="fetchPreviousPage"
+ @next="fetchNextPage"
+ />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
index d44bdd1bba2..df87ee79111 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
@@ -339,7 +339,7 @@ export default {
</template>
</template>
- <div class="gl-display-flex gl-justify-content-center">
+ <div v-if="!mutationLoading" class="gl-display-flex gl-justify-content-center">
<persisted-pagination
class="gl-mt-3"
:pagination="pageInfo"
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 4cc1ed092d2..55a60e21784 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -110,10 +110,13 @@ class Projects::EnvironmentsController < Projects::ApplicationController
return render_404 unless @environment.available?
stop_actions = @environment.stop_with_actions!(current_user)
+ job = stop_actions.first if stop_actions&.count == 1
action_or_env_url =
- if stop_actions&.count == 1
- polymorphic_url([project, stop_actions.first])
+ if job.instance_of?(::Ci::Build)
+ polymorphic_url([project, job])
+ elsif job.instance_of?(::Ci::Bridge)
+ project_pipeline_url(project, job.pipeline_id)
else
project_environment_url(project, @environment)
end
diff --git a/app/helpers/mirror_helper.rb b/app/helpers/mirror_helper.rb
index 06deaeb5e9e..158aa5e0944 100644
--- a/app/helpers/mirror_helper.rb
+++ b/app/helpers/mirror_helper.rb
@@ -15,6 +15,11 @@ module MirrorHelper
html_escape(_('Git LFS objects will be synced if LFS is %{docs_link_start}enabled for the project%{docs_link_end}. Push mirrors will %{strong_open}not%{strong_close} sync LFS objects over SSH.')) %
{ docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe, strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
end
+
+ def mirrored_repositories_count
+ count = @project.mirror == true ? 1 : 0
+ count + @project.remote_mirrors.to_a.count(&:enabled)
+ end
end
MirrorHelper.prepend_mod_with('MirrorHelper')
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 9f3832178aa..9824e88ca30 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -40,6 +40,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
vertex_project
], remove_with: '16.3', remove_after: '2023-07-22'
ignore_column :dashboard_notification_limit, remove_with: '16.5', remove_after: '2023-08-22'
+ ignore_column :database_apdex_settings, remove_with: '16.4', remove_after: '2023-08-22'
INSTANCE_REVIEW_MIN_USERS = 50
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
@@ -733,7 +734,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
validates :inactive_projects_send_warning_email_after_months,
numericality: { only_integer: true, greater_than: 0, less_than: :inactive_projects_delete_after_months }
- validates :database_apdex_settings, json_schema: { filename: 'application_setting_database_apdex_settings' }, allow_nil: true
+ validates :prometheus_alert_db_indicators_settings, json_schema: { filename: 'application_setting_prometheus_alert_db_indicators_settings' }, allow_nil: true
validates :namespace_aggregation_schedule_lease_duration_in_seconds,
numericality: { only_integer: true, greater_than: 0 }
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index ab63a4dbb47..995fabf0137 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -984,6 +984,18 @@ class MergeRequest < ApplicationRecord
branch_merge_base_commit.try(:sha)
end
+ def existing_mrs_targeting_same_branch
+ similar_mrs = target_project
+ .merge_requests
+ .where(source_branch: source_branch, target_branch: target_branch)
+ .where(source_project: source_project)
+ .opened
+
+ similar_mrs = similar_mrs.id_not_in(id) if persisted?
+
+ similar_mrs
+ end
+
def validate_branches
return unless target_project && source_project
@@ -995,25 +1007,24 @@ class MergeRequest < ApplicationRecord
[:source_branch, :target_branch].each { |attr| validate_branch_name(attr) }
if opened?
- similar_mrs = target_project
- .merge_requests
- .where(source_branch: source_branch, target_branch: target_branch)
- .where(source_project_id: source_project&.id)
- .opened
-
- similar_mrs = similar_mrs.where.not(id: id) if persisted?
-
- conflict = similar_mrs.first
+ conflicting_mr = existing_mrs_targeting_same_branch.first
- if conflict.present?
+ if conflicting_mr
errors.add(
:validate_branches,
- "Another open merge request already exists for this source branch: #{conflict.to_reference}"
+ conflicting_mr_message(conflicting_mr)
)
end
end
end
+ def conflicting_mr_message(conflicting_mr)
+ format(
+ _("Another open merge request already exists for this source branch: %{conflicting_mr_reference}"),
+ conflicting_mr_reference: conflicting_mr.to_reference
+ )
+ end
+
def validate_branch_name(attr)
return unless will_save_change_to_attribute?(attr)
diff --git a/app/validators/json_schemas/application_setting_database_apdex_settings.json b/app/validators/json_schemas/application_setting_prometheus_alert_db_indicators_settings.json
index 8b58dd44586..ccd18f3d678 100644
--- a/app/validators/json_schemas/application_setting_database_apdex_settings.json
+++ b/app/validators/json_schemas/application_setting_prometheus_alert_db_indicators_settings.json
@@ -1,5 +1,5 @@
{
- "description": "Database Apdex Settings",
+ "description": "Prometheus Alert Based Db indicators Settings",
"type": "object",
"properties": {
"prometheus_api_url": {
diff --git a/app/views/projects/merge_requests/creations/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml
index 726dc87105c..c7569dce266 100644
--- a/app/views/projects/merge_requests/creations/new.html.haml
+++ b/app/views/projects/merge_requests/creations/new.html.haml
@@ -5,7 +5,11 @@
- add_page_specific_style 'page_bundles/ci_status'
- add_page_specific_style 'page_bundles/merge_request'
-- if @merge_request.can_be_created && !params[:change_branches]
+- conflicting_mr = @merge_request.existing_mrs_targeting_same_branch.first
+
+- if @merge_request.can_be_created && !params[:change_branches] && !conflicting_mr
= render 'new_submit'
- else
+ - if conflicting_mr
+ - flash[:alert] = @merge_request.conflicting_mr_message(conflicting_mr)
= render 'new_compare'
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index 110bc8d82f8..a9a2ca6a494 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -12,28 +12,45 @@
= _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.')
= link_to _('How do I mirror repositories?'), help_page_path('user/project/repository/mirror/index.md'), target: '_blank', rel: 'noopener noreferrer'
- .settings-content
- - if mirror_settings_enabled
- = gitlab_ui_form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f|
- .panel.panel-default
- .panel-body
- %div= form_errors(@project)
-
- .form-group.has-feedback
- = label_tag :url, _('Git repository URL'), class: 'label-light'
- = text_field_tag :url, nil, class: 'form-control gl-form-input js-mirror-url js-repo-url', placeholder: _('Input the remote repository URL'), required: true, pattern: "(#{protocols}):\/\/.+", autocomplete: 'new-password', data: { qa_selector: 'mirror_repository_url_field' }
-
- = render 'projects/mirrors/instructions'
-
- = render 'projects/mirrors/mirror_repos_form', f: f
- = render 'projects/mirrors/branch_filter'
-
- .panel-footer
- = f.submit _('Mirror repository'), class: 'js-mirror-submit', name: :update_remote_mirror, pajamas_button: true, data: { qa_selector: 'mirror_repository_button' }
- - else
- = render Pajamas::AlertComponent.new(dismissible: false) do |c|
- - c.with_body do
- = _('Mirror settings are only available to GitLab administrators.')
-
- = render 'projects/mirrors/mirror_repos_list'
+ .settings-content
+ = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card js-toggle-container' }, header_options: { class: 'gl-new-card-header' }, body_options: { class: 'gl-new-card-body gl-px-0' }) do |c|
+ - c.with_header do
+ .gl-new-card-title-wrapper
+ %h5.gl-new-card-title
+ = _('Mirrored repositories')
+ .gl-new-card-count
+ = sprite_icon('earth', css_class: 'gl-mr-2')
+ %span.js-mirrored-repo-count
+ = mirrored_repositories_count
+ .gl-new-card-actions
+ = render Pajamas::ButtonComponent.new(size: :small, button_options: { class: "js-toggle-button js-toggle-content" }) do
+ = _('Add new')
+ - c.with_body do
+ - if mirror_settings_enabled
+ .gl-new-card-add-form.gl-m-3.gl-mb-4.gl-display-none.js-toggle-content
+ %h4.gl-mt-0
+ = s_('Profiles|Add new mirror repository')
+ = gitlab_ui_form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f|
+ %div= form_errors(@project)
+ .form-group.has-feedback
+ = label_tag :url, _('Git repository URL'), class: 'label-light'
+ = text_field_tag :url, nil, class: 'form-control gl-form-input js-mirror-url js-repo-url', placeholder: _('Input the remote repository URL'), required: true, pattern: "(#{protocols}):\/\/.+", autocomplete: 'new-password', data: { qa_selector: 'mirror_repository_url_field' }
+
+ = render 'projects/mirrors/instructions'
+
+ = render 'projects/mirrors/mirror_repos_form', f: f
+
+ = render 'projects/mirrors/branch_filter'
+
+ = f.submit _('Mirror repository'), class: 'js-mirror-submit', name: :update_remote_mirror, pajamas_button: true, data: { qa_selector: 'mirror_repository_button' }
+
+ = render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do
+ = _('Cancel')
+
+ - else
+ = render Pajamas::AlertComponent.new(dismissible: false) do |c|
+ - c.with_body do
+ = _('Mirror settings are only available to GitLab administrators.')
+
+ = render 'projects/mirrors/mirror_repos_list'
diff --git a/app/views/projects/mirrors/_mirror_repos_list.html.haml b/app/views/projects/mirrors/_mirror_repos_list.html.haml
index 185d86245c5..0debd13709d 100644
--- a/app/views/projects/mirrors/_mirror_repos_list.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos_list.html.haml
@@ -1,49 +1,41 @@
- mirror_settings_enabled = can?(current_user, :admin_remote_mirror, @project)
-.panel.panel-default
- .table-responsive
- - if !@project.mirror? && @project.remote_mirrors.count == 0
- = render Pajamas::CardComponent.new(card_options: { class: 'gl-mt-5' }) do |c|
- - c.with_header do
- %strong
- = _('Mirrored repositories') + ' (0)'
- - c.with_body do
- = _('There are currently no mirrored repositories.')
- - else
- %table.table.gl-table.gl-mt-5
- %thead
- %tr
- %th
- = _('Mirrored repositories')
- = render_if_exists 'projects/mirrors/mirrored_repositories_count'
- %th= _('Direction')
- %th= _('Last update attempt')
- %th= _('Last successful update')
- %th
- %th
- %tbody.js-mirrors-table-body
- = render_if_exists 'projects/mirrors/table_pull_row'
- - @project.remote_mirrors.each_with_index do |mirror, index|
- - next if mirror.new_record?
- %tr.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?), data: { qa_selector: 'mirrored_repository_row_container' } }
- %td{ data: { qa_selector: 'mirror_repository_url_content' } }
- = mirror.safe_url || _('Invalid URL')
- = render_if_exists 'projects/mirrors/mirror_branches_setting_badge', record: mirror
- %td= _('Push')
- %td
- = mirror.last_update_started_at.present? ? time_ago_with_tooltip(mirror.last_update_started_at) : _('Never')
- %td{ data: { qa_selector: 'mirror_last_update_at_content' } }= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
- %td
- - if mirror.disabled?
- = render 'projects/mirrors/disabled_mirror_badge'
- - if mirror.last_error.present?
- = gl_badge_tag _('Error'), { variant: :danger }, { data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge_content' }, title: html_escape(mirror.last_error.try(:strip)) }
- %td.gl-display-flex
- - if mirror_settings_enabled
- .btn-group.mirror-actions-group{ role: 'group' }
- - if mirror.ssh_key_auth?
- = clipboard_button(text: mirror.ssh_public_key, class: 'gl-button btn btn-default btn-icon', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button')
- = render 'shared/remote_mirror_update_button', remote_mirror: mirror
- = render Pajamas::ButtonComponent.new(variant: :danger,
- icon: 'remove',
- button_options: { class: 'js-delete-mirror rspec-delete-mirror', title: _('Remove'), data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' } })
+.table-responsive.gl-mb-0
+ - if !@project.mirror? && @project.remote_mirrors.count == 0
+ .gl-new-card-empty.gl-px-5.gl-py-4= _('There are currently no mirrored repositories.')
+ - else
+ %table.table.b-table.gl-table.b-table-stacked-md
+ %thead.d-none.d-md-table-header-group
+ %tr
+ %th= _('Repository')
+ %th= _('Direction')
+ %th= _('Last update attempt')
+ %th= _('Last successful update')
+ %th
+ %th
+ %tbody.js-mirrors-table-body
+ = render_if_exists 'projects/mirrors/table_pull_row'
+ - @project.remote_mirrors.each_with_index do |mirror, index|
+ - next if mirror.new_record?
+ %tr.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?), data: { qa_selector: 'mirrored_repository_row_container' } }
+ %td{ data: { qa_selector: 'mirror_repository_url_content' } }
+ = mirror.safe_url || _('Invalid URL')
+ = render_if_exists 'projects/mirrors/mirror_branches_setting_badge', record: mirror
+ %td= _('Push')
+ %td
+ = mirror.last_update_started_at.present? ? time_ago_with_tooltip(mirror.last_update_started_at) : _('Never')
+ %td{ data: { qa_selector: 'mirror_last_update_at_content' } }= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
+ %td
+ - if mirror.disabled?
+ = render 'projects/mirrors/disabled_mirror_badge'
+ - if mirror.last_error.present?
+ = gl_badge_tag _('Error'), { variant: :danger }, { data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge_content' }, title: html_escape(mirror.last_error.try(:strip)) }
+ %td
+ - if mirror_settings_enabled
+ .btn-group.mirror-actions-group{ role: 'group' }
+ - if mirror.ssh_key_auth?
+ = clipboard_button(text: mirror.ssh_public_key, class: 'gl-button btn btn-default btn-icon', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button')
+ = render 'shared/remote_mirror_update_button', remote_mirror: mirror
+ = render Pajamas::ButtonComponent.new(variant: :danger,
+ icon: 'remove',
+ button_options: { class: 'js-delete-mirror rspec-delete-mirror', title: _('Remove'), data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' } })
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 703f057d9a5..19f5ae4e6b3 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -73,7 +73,7 @@
= _("Pipeline triggers")
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
- %p
+ %p.gl-text-secondary
= _("Trigger a pipeline for a branch or tag by generating a trigger token and using it with an API call. The token impersonates a user's project access and permissions.")
= link_to _('Learn more.'), help_page_path('ci/triggers/index'), target: '_blank', rel: 'noopener noreferrer'
.settings-content