diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-17 18:08:05 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-17 18:08:05 +0000 |
commit | 184c2ced0761bd8dd7032619d16d3983fed7944a (patch) | |
tree | cc82b32ee7c1797509da3cf384617e4ffa2e1733 /app | |
parent | 238d22c07218adf2b8f3db630ee8b74ca6f29df5 (diff) | |
download | gitlab-ce-184c2ced0761bd8dd7032619d16d3983fed7944a.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
26 files changed, 299 insertions, 143 deletions
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index b4b124d5db1..859f839741f 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -130,6 +130,10 @@ export default { return title; }, + + shouldRenderHeaderCallout() { + return this.shouldRenderCalloutMessage && !this.hasUnmetPrerequisitesFailure; + }, }, watch: { // Once the job log is loaded, @@ -239,10 +243,9 @@ export default { /> </div> - <callout - v-if="shouldRenderCalloutMessage && !hasUnmetPrerequisitesFailure" - :message="job.callout_message" - /> + <callout v-if="shouldRenderHeaderCallout"> + <div v-html="job.callout_message"></div> + </callout> </header> <!-- EO Header Section --> diff --git a/app/assets/javascripts/lib/utils/set.js b/app/assets/javascripts/lib/utils/set.js new file mode 100644 index 00000000000..3845d648b61 --- /dev/null +++ b/app/assets/javascripts/lib/utils/set.js @@ -0,0 +1,9 @@ +/** + * Checks if the first argument is a subset of the second argument. + * @param {Set} subset The set to be considered as the subset. + * @param {Set} superset The set to be considered as the superset. + * @returns {boolean} + */ +// eslint-disable-next-line import/prefer-default-export +export const isSubset = (subset, superset) => + Array.from(subset).every(value => superset.has(value)); diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index ed48331f459..95f8270b5d0 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -1,6 +1,13 @@ <script> import { mapActions, mapGetters } from 'vuex'; -import { GlLoadingIcon, GlButton, GlTooltipDirective, GlModal, GlModalDirective } from '@gitlab/ui'; +import { + GlLoadingIcon, + GlButton, + GlTooltipDirective, + GlModal, + GlModalDirective, + GlEmptyState, +} from '@gitlab/ui'; import createFlash from '../../flash'; import ClipboardButton from '../../vue_shared/components/clipboard_button.vue'; import Icon from '../../vue_shared/components/icon.vue'; @@ -17,6 +24,7 @@ export default { GlButton, Icon, GlModal, + GlEmptyState, }, directives: { GlTooltip: GlTooltipDirective, @@ -103,10 +111,18 @@ export default { <div v-else-if="!repo.isLoading && isOpen" class="container-image-tags"> <table-registry v-if="repo.list.length" :repo="repo" :can-delete-repo="canDeleteRepo" /> - - <div v-else class="nothing-here-block"> - {{ s__('ContainerRegistry|No tags in Container Registry for this container image.') }} - </div> + <gl-empty-state + v-else + :title="s__('ContainerRegistry|This image has no active tags')" + :description=" + s__( + `ContainerRegistry|The last tag related to this image was recently removed. + This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process. + If you have any questions, contact your administrator.`, + ) + " + class="mx-auto my-0" + /> </div> <gl-modal :modal-id="modalId" ok-variant="danger" @ok="handleDeleteRepository"> <template v-slot:modal-title>{{ s__('ContainerRegistry|Remove repository') }}</template> diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue index ac7272c4d29..8470fbc2b59 100644 --- a/app/assets/javascripts/registry/components/table_registry.vue +++ b/app/assets/javascripts/registry/components/table_registry.vue @@ -43,6 +43,7 @@ export default { }, data() { return { + selectedItems: [], itemsToBeDeleted: [], modalId: `confirm-image-deletion-modal-${this.repo.id}`, selectAllChecked: false, @@ -96,6 +97,7 @@ export default { }, deleteSingleItem(index) { this.setModalDescription(index); + this.itemsToBeDeleted = [index]; this.$refs.deleteModal.$refs.modal.$once('ok', () => { this.removeModalEvents(); @@ -103,9 +105,10 @@ export default { }); }, deleteMultipleItems() { - if (this.itemsToBeDeleted.length === 1) { + this.itemsToBeDeleted = [...this.selectedItems]; + if (this.selectedItems.length === 1) { this.setModalDescription(this.itemsToBeDeleted[0]); - } else if (this.itemsToBeDeleted.length > 1) { + } else if (this.selectedItems.length > 1) { this.setModalDescription(); } @@ -115,6 +118,7 @@ export default { }); }, handleSingleDelete(itemToDelete) { + this.itemsToBeDeleted = []; this.deleteItem(itemToDelete) .then(() => this.fetchList({ repo: this.repo })) .catch(() => this.showError(errorMessagesTypes.DELETE_REGISTRY)); @@ -122,6 +126,7 @@ export default { handleMultipleDelete() { const { itemsToBeDeleted } = this; this.itemsToBeDeleted = []; + this.selectedItems = []; if (this.bulkDeletePath) { this.multiDeleteItems({ @@ -150,23 +155,23 @@ export default { } }, selectAll() { - this.itemsToBeDeleted = this.repo.list.map((x, index) => index); + this.selectedItems = this.repo.list.map((x, index) => index); this.selectAllChecked = true; }, deselectAll() { - this.itemsToBeDeleted = []; + this.selectedItems = []; this.selectAllChecked = false; }, - updateItemsToBeDeleted(index) { - const delIndex = this.itemsToBeDeleted.findIndex(x => x === index); + updateselectedItems(index) { + const delIndex = this.selectedItems.findIndex(x => x === index); if (delIndex > -1) { - this.itemsToBeDeleted.splice(delIndex, 1); + this.selectedItems.splice(delIndex, 1); this.selectAllChecked = false; } else { - this.itemsToBeDeleted.push(index); + this.selectedItems.push(index); - if (this.itemsToBeDeleted.length === this.repo.list.length) { + if (this.selectedItems.length === this.repo.list.length) { this.selectAllChecked = true; } } @@ -199,7 +204,7 @@ export default { v-if="canDeleteRepo" v-gl-tooltip v-gl-modal="modalId" - :disabled="!itemsToBeDeleted || itemsToBeDeleted.length === 0" + :disabled="!selectedItems || selectedItems.length === 0" class="js-delete-registry float-right" data-track-event="click_button" data-track-label="bulk_registry_tag_delete" @@ -219,8 +224,8 @@ export default { <gl-form-checkbox v-if="canDeleteRow(item)" class="js-select-checkbox" - :checked="itemsToBeDeleted && itemsToBeDeleted.includes(index)" - @change="updateItemsToBeDeleted(index)" + :checked="selectedItems && selectedItems.includes(index)" + @change="updateselectedItems(index)" /> </td> <td class="monospace"> diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss index cbd390e7145..7dd7ab339dd 100644 --- a/app/assets/stylesheets/framework/blank.scss +++ b/app/assets/stylesheets/framework/blank.scss @@ -14,13 +14,12 @@ .blank-state-row { display: flex; flex-wrap: wrap; - justify-content: space-around; - height: 100%; + justify-content: space-between; } .blank-state-welcome { text-align: center; - padding: 20px 0 40px; + padding: $gl-padding 0 ($gl-padding * 2); .blank-state-welcome-title { font-size: 24px; @@ -32,23 +31,9 @@ } .blank-state-link { - display: block; color: $gl-text-color; - flex: 0 0 100%; margin-bottom: 15px; - @include media-breakpoint-up(sm) { - flex: 0 0 49%; - - &:nth-child(odd) { - margin-right: 5px; - } - - &:nth-child(even) { - margin-left: 5px; - } - } - &:hover { background-color: $gray-light; text-decoration: none; @@ -63,15 +48,25 @@ } .blank-state { - padding: 20px; + display: flex; + align-items: center; + padding: 20px 50px; border: 1px solid $border-color; border-radius: $border-radius-default; + min-height: 240px; + margin-bottom: $gl-padding; + width: calc(50% - #{$gl-padding-8}); + + @include media-breakpoint-down(sm) { + width: 100%; + flex-direction: column; + justify-content: center; + padding: 50px 20px; + + .column-small & { + width: 100%; + } - @include media-breakpoint-up(sm) { - display: flex; - height: 100%; - align-items: center; - padding: 50px 30px; } } @@ -90,7 +85,7 @@ } .blank-state-body { - @include media-breakpoint-down(xs) { + @include media-breakpoint-down(sm) { text-align: center; margin-top: 20px; } @@ -121,9 +116,3 @@ } } } - -@include media-breakpoint-down(xs) { - .blank-state-icon svg { - width: 315px; - } -} diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 9028bfa8ec9..3876d1c10d4 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -69,10 +69,6 @@ details { margin-bottom: $gl-padding; - - summary { - margin-bottom: $gl-padding; - } } // Single code lines should wrap diff --git a/app/controllers/groups/registry/repositories_controller.rb b/app/controllers/groups/registry/repositories_controller.rb index 39f6963ee0a..e09a9e6eb21 100644 --- a/app/controllers/groups/registry/repositories_controller.rb +++ b/app/controllers/groups/registry/repositories_controller.rb @@ -4,6 +4,7 @@ module Groups class RepositoriesController < Groups::ApplicationController before_action :verify_container_registry_enabled! before_action :authorize_read_container_image! + before_action :feature_flag_group_container_registry_browser! def index track_event(:list_repositories) @@ -22,6 +23,10 @@ module Groups private + def feature_flag_group_container_registry_browser! + render_404 unless Feature.enabled?(:group_container_registry_browser, group) + end + def verify_container_registry_enabled! render_404 unless Gitlab.config.registry.enabled end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 811467ca03a..6ddcbf61090 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -22,7 +22,9 @@ module GroupsHelper end def group_container_registry_nav? - Gitlab.config.registry.enabled && can?(current_user, :read_container_image, @group) + Gitlab.config.registry.enabled && + can?(current_user, :read_container_image, @group) && + Feature.enabled?(:group_container_registry_browser, @group) end def group_sidebar_links diff --git a/app/mailers/emails/releases.rb b/app/mailers/emails/releases.rb new file mode 100644 index 00000000000..137858d31e8 --- /dev/null +++ b/app/mailers/emails/releases.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Emails + module Releases + def new_release_email(user_id, release, reason = nil) + @release = release + @project = @release.project + @target_url = namespace_project_releases_url( + namespace_id: @project.namespace, + project_id: @project + ) + + user = User.find(user_id) + + mail( + to: user.notification_email_for(@project.group), + subject: subject(release_email_subject) + ) + end + + private + + def release_email_subject + release_info = [@release.name, @release.tag].select(&:presence).join(' - ') + "New release: #{release_info}" + end + end +end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index d0b43b4397f..c7cfefeec9b 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -16,6 +16,7 @@ class Notify < BaseMailer include Emails::Members include Emails::AutoDevops include Emails::RemoteMirrors + include Emails::Releases helper MilestonesHelper helper MergeRequestsHelper diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index b016aa8e477..c48ab28ce73 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -754,6 +754,10 @@ module Ci true end + def invalid_dependencies + dependencies.reject(&:valid_dependency?) + end + def runner_required_feature_names strong_memoize(:runner_required_feature_names) do RUNNER_FEATURES.select do |feature, method| diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 981590b688f..20160da62d2 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -25,6 +25,7 @@ class NotificationSetting < ApplicationRecord end EMAIL_EVENTS = [ + :new_release, :new_note, :new_issue, :reopen_issue, diff --git a/app/models/release.rb b/app/models/release.rb index add57367f61..5a7bfe2d495 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -26,10 +26,12 @@ class Release < ApplicationRecord validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") } scope :sorted, -> { order(released_at: :desc) } + scope :with_project_and_namespace, -> { includes(project: :namespace) } delegate :repository, to: :project after_commit :create_evidence!, on: :create + after_commit :notify_new_release, on: :create def commit strong_memoize(:commit) do @@ -73,6 +75,10 @@ class Release < ApplicationRecord def create_evidence! CreateEvidenceWorker.perform_async(self.id) end + + def notify_new_release + NewReleaseWorker.perform_async(id) + end end Release.prepend_if_ee('EE::Release') diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 0c754157267..480a8cab6ff 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -121,4 +121,28 @@ class BuildDetailsEntity < JobEntity def can_admin_build? can?(request.current_user, :admin_build, project) end + + def callout_message + return super unless build.failure_reason.to_sym == :missing_dependency_failure + + docs_url = "https://docs.gitlab.com/ce/ci/yaml/README.html#dependencies" + + [ + failure_message.html_safe, + help_message(docs_url).html_safe + ].join("<br />") + end + + def invalid_dependencies + build.invalid_dependencies.map(&:name).join(', ') + end + + def failure_message + _("This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}") % + { invalid_dependencies: invalid_dependencies } + end + + def help_message(docs_url) + _("Please refer to <a href=\"%{docs_url}\">%{docs_url}</a>") % { docs_url: docs_url } + end end diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index fca64270cae..9afbb678f5d 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -28,6 +28,10 @@ module NotificationRecipientService Builder::ProjectMaintainers.new(*args).notification_recipients end + def self.build_new_release_recipients(*args) + Builder::NewRelease.new(*args).notification_recipients + end + module Builder class Base def initialize(*) @@ -359,6 +363,26 @@ module NotificationRecipientService end end + class NewRelease < Base + attr_reader :target + + def initialize(target) + @target = target + end + + def build! + add_recipients(target.project.authorized_users, :custom, nil) + end + + def custom_action + :new_release + end + + def acting_user + target.author + end + end + class MergeRequestUnmergeable < Base attr_reader :target def initialize(merge_request) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index ed357aa0392..b56b2cf14e3 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -289,6 +289,15 @@ class NotificationService end end + # Notify users when a new release is created + def send_new_release_notifications(release) + recipients = NotificationRecipientService.build_new_release_recipients(release) + + recipients.each do |recipient| + mailer.new_release_email(recipient.user.id, release, recipient.reason).deliver_later + end + end + # Members def new_access_request(member) return true unless member.notifiable?(:subscription) diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 545e53e6b09..2bf2b5fce8d 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -45,12 +45,11 @@ = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do .filtered-search-wrapper.d-flex .filtered-search-box - = dropdown_tag(custom_icon('icon_history'), + = dropdown_tag(_('Recent searches'), options: { wrapper_class: 'filtered-search-history-dropdown-wrapper', toggle_class: 'filtered-search-history-dropdown-toggle-button', dropdown_class: 'filtered-search-history-dropdown', - content_class: 'filtered-search-history-dropdown-content', - title: _('Recent searches') }) do + content_class: 'filtered-search-history-dropdown-content' }) do .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } } .filtered-search-box-input-container.droplab-dropdown .scroll-container diff --git a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml index c50b20a83dc..6e7ec1264ea 100644 --- a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml +++ b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml @@ -1,41 +1,40 @@ .blank-state-row - = link_to new_project_path, class: "blank-state-link" do - .blank-state + - if has_start_trial? + = render_if_exists "dashboard/projects/blank_state_ee_trial" + + = link_to new_project_path, class: "blank-state blank-state-link" do + .blank-state-icon + = image_tag("illustrations/welcome/add_new_project") + .blank-state-body + %h3.blank-state-title + Create a project + %p.blank-state-text + Projects are where you store your code, access issues, wiki and other features of GitLab. + + - if current_user.can_create_group? + = link_to new_group_path, class: "blank-state blank-state-link" do .blank-state-icon - = custom_icon("add_new_project", size: 50) + = image_tag("illustrations/welcome/add_new_group") .blank-state-body %h3.blank-state-title - Create a project + Create a group %p.blank-state-text - Projects are where you store your code, access issues, wiki and other features of GitLab. + Groups are a great way to organize projects and people. - - if current_user.can_create_group? - = link_to new_group_path, class: "blank-state-link" do - .blank-state - .blank-state-icon - = custom_icon("add_new_group", size: 50) - .blank-state-body - %h3.blank-state-title - Create a group - %p.blank-state-text - Groups are a great way to organize projects and people. - - = link_to new_admin_user_path, class: "blank-state-link" do - .blank-state - .blank-state-icon - = custom_icon("add_new_user", size: 50) - .blank-state-body - %h3.blank-state-title - Add people - %p.blank-state-text - Add your team members and others to GitLab. - - = link_to admin_root_path, class: "blank-state-link" do - .blank-state + = link_to new_admin_user_path, class: "blank-state blank-state-link" do .blank-state-icon - = custom_icon("configure_server", size: 50) + = image_tag("illustrations/welcome/add_new_user") .blank-state-body %h3.blank-state-title - Configure GitLab + Add people %p.blank-state-text - Make adjustments to how your GitLab instance is set up. + Add your team members and others to GitLab. + + = link_to admin_root_path, class: "blank-state blank-state-link" do + .blank-state-icon + = image_tag("illustrations/welcome/configure_server") + .blank-state-body + %h3.blank-state-title + Configure GitLab + %p.blank-state-text + Make adjustments to how your GitLab instance is set up. diff --git a/app/views/dashboard/projects/_blank_state_welcome.html.haml b/app/views/dashboard/projects/_blank_state_welcome.html.haml index 8d5bddbb288..e3af3405b76 100644 --- a/app/views/dashboard/projects/_blank_state_welcome.html.haml +++ b/app/views/dashboard/projects/_blank_state_welcome.html.haml @@ -2,19 +2,18 @@ .blank-state-row - if current_user.can_create_project? - = link_to new_project_path, class: "blank-state-link" do - .blank-state - .blank-state-icon - = custom_icon("add_new_project", size: 50) - .blank-state-body - %h3.blank-state-title - Create a project - %p.blank-state-text - Projects are where you store your code, access issues, wiki and other features of GitLab. + = link_to new_project_path, class: "blank-state blank-state-link" do + .blank-state-icon + = image_tag("illustrations/welcome/add_new_project") + .blank-state-body + %h3.blank-state-title + Create a project + %p.blank-state-text + Projects are where you store your code, access issues, wiki and other features of GitLab. - else .blank-state .blank-state-icon - = custom_icon("add_new_project", size: 50) + = image_tag("illustrations/welcome/add_new_project") .blank-state-body %h3.blank-state-title Create a project @@ -22,37 +21,34 @@ If you are added to a project, it will be displayed here. - if current_user.can_create_group? - = link_to new_group_path, class: "blank-state-link" do - .blank-state - .blank-state-icon - = custom_icon("add_new_group", size: 50) - .blank-state-body - %h3.blank-state-title - Create a group - %p.blank-state-text - Groups are the best way to manage projects and members. + = link_to new_group_path, class: "blank-state blank-state-link" do + .blank-state-icon + = image_tag("illustrations/welcome/add_new_group") + .blank-state-body + %h3.blank-state-title + Create a group + %p.blank-state-text + Groups are the best way to manage projects and members. - if public_project_count > 0 - = link_to trending_explore_projects_path, class: "blank-state-link" do - .blank-state - .blank-state-icon - = custom_icon("globe", size: 50) - .blank-state-body - %h3.blank-state-title - Explore public projects - %p.blank-state-text - There are - = number_with_delimiter(public_project_count) - public projects on this server. - Public projects are an easy way to allow - everyone to have read-only access. - - = link_to "https://docs.gitlab.com/", class: "blank-state-link" do - .blank-state + = link_to trending_explore_projects_path, class: "blank-state blank-state-link" do .blank-state-icon - = custom_icon("lightbulb", size: 50) + = image_tag("illustrations/welcome/globe") .blank-state-body %h3.blank-state-title - Learn more about GitLab + Explore public projects %p.blank-state-text - Take a look at the documentation to discover all of GitLab's capabilities. + There are + = number_with_delimiter(public_project_count) + public projects on this server. + Public projects are an easy way to allow + everyone to have read-only access. + + = link_to "https://docs.gitlab.com/", class: "blank-state blank-state-link" do + .blank-state-icon + = image_tag("illustrations/welcome/lightbulb") + .blank-state-body + %h3.blank-state-title + Learn more about GitLab + %p.blank-state-text + Take a look at the documentation to discover all of GitLab's capabilities. diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index eff68f817bb..a2b1f0d9298 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -1,4 +1,4 @@ -.blank-state-parent-container{ class: ('has-start-trial-container' if has_start_trial?) } +.blank-state-parent-container .section-container.section-welcome{ class: "#{ 'section-admin-welcome' if current_user.admin? }" } .container.section-body .row @@ -7,12 +7,7 @@ = _('Welcome to GitLab') %p.blank-state-text = _('Faster releases. Better code. Less pain.') - .blank-state-row - %div{ class: ('column-large' if has_start_trial?) } - - if current_user.admin? - = render "blank_state_admin_welcome" - - else - = render "blank_state_welcome" - - if has_start_trial? - .column-small - = render_if_exists "blank_state_ee_trial" + - if current_user.admin? + = render "blank_state_admin_welcome" + - else + = render "blank_state_welcome" diff --git a/app/views/notify/new_release_email.html.haml b/app/views/notify/new_release_email.html.haml new file mode 100644 index 00000000000..45e99f3c07a --- /dev/null +++ b/app/views/notify/new_release_email.html.haml @@ -0,0 +1,18 @@ +- release_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url } +- description_details = { tag: @release.tag, name: @project.name, release_link_start: release_link_start, release_link_end: '</a>'.html_safe } + +%div{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %p + = _("A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it.").html_safe % description_details + + %p + %h4= _("Assets:") + %ul + - @release.links.each do |link| + %li= link_to(link.name, link.url) + - @release.sources.each do |source| + %li= link_to(_("Download %{format}") % { format: source.format }, source.url) + + %p + %h4= _("Release notes:") + = markdown_field(@release, :description) diff --git a/app/views/notify/new_release_email.text.erb b/app/views/notify/new_release_email.text.erb new file mode 100644 index 00000000000..e03cf2d5fd1 --- /dev/null +++ b/app/views/notify/new_release_email.text.erb @@ -0,0 +1,12 @@ +<%= _("A new Release %{tag} for %{name} was published. Visit the Releases page to read more about it:").html_safe % { tag: @release.tag, name: @project.name } %> <%= @target_url %> + +<%= _("Assets:") %> +<% @release.links.each do |link| -%> + - <%= link.name %>: <%= link.url %> +<% end -%> +<% @release.sources.each do |source| -%> + - <%= _("Download %{format}:") % { format: source.format } %> <%= source.url %> +<% end -%> + +<%= _("Release notes:") %> +<%= @release.description %> diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index a674136e791..66ed1cadf6a 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -51,10 +51,10 @@ %hr .form-group - = f.label :ci_config_path, _('Custom CI config path'), class: 'label-bold' + = f.label :ci_config_path, _('Custom CI configuration path'), class: 'label-bold' = f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml' %p.form-text.text-muted - = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>").html_safe + = _("The path to the CI configuration file. Defaults to <code>.gitlab-ci.yml</code>").html_safe = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank' %hr diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 9165147ef2a..9d580930fb8 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -17,12 +17,11 @@ .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row .filtered-search-box - if type != :boards_modal && type != :boards - = dropdown_tag(custom_icon('icon_history'), + = dropdown_tag(_('Recent searches'), options: { wrapper_class: "filtered-search-history-dropdown-wrapper", toggle_class: "filtered-search-history-dropdown-toggle-button", dropdown_class: "filtered-search-history-dropdown", - content_class: "filtered-search-history-dropdown-content", - title: "Recent searches" }) do + content_class: "filtered-search-history-dropdown-content" }) do .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } } .filtered-search-box-input-container.droplab-dropdown .scroll-container diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index cd8d1d05d8b..b161cc65602 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -119,6 +119,8 @@ - container_repository:delete_container_repository - container_repository:cleanup_container_repository +- notifications:new_release + - default - mailers # ActionMailer::DeliveryJob.queue_name diff --git a/app/workers/new_release_worker.rb b/app/workers/new_release_worker.rb new file mode 100644 index 00000000000..b80553a60db --- /dev/null +++ b/app/workers/new_release_worker.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class NewReleaseWorker + include ApplicationWorker + + queue_namespace :notifications + + def perform(release_id) + release = Release.with_project_and_namespace.find_by_id(release_id) + return unless release + + NotificationService.new.send_new_release_notifications(release) + end +end |