Commit 9b98dbb4 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents f996e767 5912f3de
......@@ -7,4 +7,5 @@ fragment TimelogFragment on Timelog {
note {
body
}
summary
}
......@@ -62,8 +62,8 @@ export default {
formatDate(date) {
return formatDate(date, TIME_DATE_FORMAT);
},
getNote(note) {
return note?.body;
getSummary(summary, note) {
return summary ?? note?.body;
},
getTotalTimeSpent() {
const seconds = this.report.reduce((acc, item) => acc + item.timeSpent, 0);
......@@ -81,7 +81,7 @@ export default {
{ key: 'spentAt', label: __('Spent At'), sortable: true },
{ key: 'user', label: __('User'), sortable: true },
{ key: 'timeSpent', label: __('Time Spent'), sortable: true },
{ key: 'note', label: __('Note'), sortable: true },
{ key: 'summary', label: __('Summary / Note'), sortable: true },
],
};
</script>
......@@ -107,8 +107,8 @@ export default {
<div>{{ getTotalTimeSpent() }}</div>
</template>
<template #cell(note)="{ item: { note } }">
<div>{{ getNote(note) }}</div>
<template #cell(summary)="{ item: { summary, note } }">
<div>{{ getSummary(summary, note) }}</div>
</template>
<template #foot(note)>&nbsp;</template>
</gl-table>
......
......@@ -36,6 +36,10 @@ module Types
null: true,
description: 'The note where the quick action to add the logged time was executed.'
field :summary, GraphQL::Types::String,
null: true,
description: 'The summary of how the time was spent.'
def user
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find
end
......
......@@ -175,6 +175,8 @@ class Member < ApplicationRecord
after_update :post_update_hook, unless: [:pending?, :importing?], if: :hook_prerequisites_met?
after_destroy :destroy_notification_setting
after_destroy :post_destroy_hook, unless: :pending?, if: :hook_prerequisites_met?
after_save :log_invitation_token_cleanup
after_commit :refresh_member_authorized_projects
default_value_for :notification_level, NotificationSetting.levels[:global]
......@@ -449,6 +451,13 @@ class Member < ApplicationRecord
def project_bot?
user&.project_bot?
end
def log_invitation_token_cleanup
return true unless Gitlab.com? && invite? && invite_accepted_at?
error = StandardError.new("Invitation token is present but invite was already accepted!")
Gitlab::ErrorTracking.track_exception(error, attributes.slice(%w["invite_accepted_at created_at source_type source_id user_id id"]))
end
end
Member.prepend_mod_with('Member')
......@@ -587,7 +587,7 @@ class Note < ApplicationRecord
end
def post_processed_cache_key
cache_key_items = [cache_key]
cache_key_items = [cache_key, author.cache_key]
cache_key_items << Digest::SHA1.hexdigest(redacted_note_html) if redacted_note_html.present?
cache_key_items.join(':')
......
......@@ -11,11 +11,12 @@ module Ci
Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
Gitlab::Ci::Pipeline::Chain::Validate::Repository,
Gitlab::Ci::Pipeline::Chain::Validate::SecurityOrchestrationPolicy,
Gitlab::Ci::Pipeline::Chain::Skip,
Gitlab::Ci::Pipeline::Chain::Config::Content,
Gitlab::Ci::Pipeline::Chain::Config::Process,
Gitlab::Ci::Pipeline::Chain::Validate::AfterConfig,
Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
Gitlab::Ci::Pipeline::Chain::Skip,
Gitlab::Ci::Pipeline::Chain::LegacySkip,
Gitlab::Ci::Pipeline::Chain::SeedBlock,
Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
Gitlab::Ci::Pipeline::Chain::Seed,
......
......@@ -28,11 +28,12 @@ module Users
params[:emoji] = UserStatus::DEFAULT_EMOJI if params[:emoji].blank?
params[:availability] = UserStatus.availabilities[:not_set] unless new_user_availability
user_status.update(params)
bump_user if user_status.update(params)
end
def remove_status
UserStatus.delete(target_user.id)
bump_user if UserStatus.delete(target_user.id).nonzero?
true
end
def user_status
......@@ -48,5 +49,12 @@ module Users
def new_user_availability
UserStatus.availabilities[params[:availability]]
end
def bump_user
# Intentionally not calling `touch` as that will trigger other callbacks
# on target_user (e.g. after_touch, after_commit, after_rollback) and we
# don't need them to happen here.
target_user.update_column(:updated_at, Time.current)
end
end
end
= render_if_exists "layouts/nav/ee/security_link" # EE-specific
= render_if_exists "layouts/nav/ee/push_rules_link" # EE-specific
- if group_sidebar_link?(:runners)
......
---
name: ci_skip_before_parsing_yaml
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66147
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337167
milestone: '14.2'
type: development
group: group::pipeline execution
default_enabled: false
......@@ -13482,6 +13482,7 @@ Represents a historically accurate report about the timebox.
| <a id="timelogmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | The merge request that logged time was added to. |
| <a id="timelognote"></a>`note` | [`Note`](#note) | The note where the quick action to add the logged time was executed. |
| <a id="timelogspentat"></a>`spentAt` | [`Time`](#time) | Timestamp of when the time tracked was spent at. |
| <a id="timelogsummary"></a>`summary` | [`String`](#string) | The summary of how the time was spent. |
| <a id="timelogtimespent"></a>`timeSpent` | [`Int!`](#int) | The time spent displayed in seconds. |
| <a id="timeloguser"></a>`user` | [`UserCore!`](#usercore) | The user that logged the time. |
......
doc/user/workspace/img/1.3-Admin.png

31.8 KB | W: | H:

doc/user/workspace/img/1.3-Admin.png

15.7 KB | W: | H:

doc/user/workspace/img/1.3-Admin.png
doc/user/workspace/img/1.3-Admin.png
doc/user/workspace/img/1.3-Admin.png
doc/user/workspace/img/1.3-Admin.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -11,5 +11,12 @@ module EE
show_discover_project_security: show_discover_project_security?(project)
})
end
override :group_sidebar_context_data
def group_sidebar_context_data(group, user)
super.merge(
show_discover_group_security: show_discover_group_security?(group)
)
end
end
end
# frozen_string_literal: true
module Groups::SecurityFeaturesHelper
def group_level_security_dashboard_available?(group)
group.licensed_feature_available?(:security_dashboard)
end
def group_level_compliance_dashboard_available?(group)
group.licensed_feature_available?(:group_level_compliance_dashboard) &&
can?(current_user, :read_group_compliance_dashboard, group)
......@@ -20,23 +16,6 @@ module Groups::SecurityFeaturesHelper
group.enforced_group_managed_accounts?
end
def primary_group_level_security_feature_path(group)
if group_level_security_dashboard_available?(group)
group_security_dashboard_path(group)
elsif group_level_compliance_dashboard_available?(group)
group_security_compliance_dashboard_path(group)
elsif group_level_credentials_inventory_available?(group)
group_security_credentials_path(group)
elsif group_level_audit_events_available?(group)
group_audit_events_path(group)
end
end
def group_level_audit_events_available?(group)
group.licensed_feature_available?(:audit_events) &&
can?(current_user, :read_group_audit_events, group)
end
def group_level_security_dashboard_data(group)
{
projects_endpoint: expose_url(api_v4_groups_projects_path(id: group.id)),
......
- main_path = primary_group_level_security_feature_path(@group)
- if main_path.present?
= nav_link(path: %w[dashboard#show vulnerabilities#index compliance_dashboards#show credentials#index audit_events#index]) do
= link_to main_path, data: { qa_selector: 'security_compliance_link' }, class: 'has-sub-items' do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security & Compliance')
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_secure_submenu' } }
= nav_link(path: %w[dashboard#show vulnerabilities#index compliance_dashboards#show credentials#index audit_events#index], html_options: { class: "fly-out-top-item" } ) do
%span.fly-out-top-item-container
%strong.fly-out-top-item-name
= _('Security & Compliance')
%li.divider.fly-out-top-item
- if group_level_security_dashboard_available?(@group)
= nav_link(path: 'dashboard#show') do
= link_to group_security_dashboard_path(@group), title: _('Security Dashboard'), data: { qa_selector: 'security_dashboard_link' } do
%span= _('Security Dashboard')
- if group_level_security_dashboard_available?(@group)
= nav_link(path: 'vulnerabilities#index') do
= link_to group_security_vulnerabilities_path(@group), title: _('Vulnerability Report'), data: { qa_selector: 'vulnerability_report_link' } do
%span= _('Vulnerability Report')
- if group_level_compliance_dashboard_available?(@group)
= nav_link(path: 'compliance_dashboards#show') do
= link_to group_security_compliance_dashboard_path(@group), title: _('Compliance') do
%span= _('Compliance')
- if group_level_credentials_inventory_available?(@group)
= nav_link(path: 'credentials#index') do
= link_to group_security_credentials_path(@group), title: _('Credentials') do
%span= _('Credentials')
- if group_level_audit_events_available?(@group)
= nav_link(path: 'audit_events#index') do
= link_to group_audit_events_path(@group), title: _('Audit Events'), data: { qa_selector: 'audit_events_settings_link' } do
%span= _('Audit Events')
- elsif show_discover_group_security?(@group)
= nav_link(path: group_security_discover_path(@group)) do
= link_to group_security_discover_path(@group) do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security')
......@@ -12,6 +12,7 @@ module EE
insert_menu_before(::Sidebars::Groups::Menus::GroupInformationMenu, ::Sidebars::Groups::Menus::TrialExperimentMenu.new(context))
insert_menu_after(::Sidebars::Groups::Menus::GroupInformationMenu, ::Sidebars::Groups::Menus::EpicsMenu.new(context))
insert_menu_after(::Sidebars::Groups::Menus::MergeRequestsMenu, ::Sidebars::Groups::Menus::SecurityComplianceMenu.new(context))
end
end
end
......
# frozen_string_literal: true
module Sidebars
module Groups
module Menus
class SecurityComplianceMenu < ::Sidebars::Menu
override :configure_menu_items
def configure_menu_items
add_item(security_dashboard_menu_item)
add_item(vulnerability_report_menu_item)
add_item(compliance_menu_item)
add_item(credentials_menu_item)
add_item(audit_events_menu_item)
true
end
override :link
def link
return renderable_items.first.link if renderable_items.any?
group_security_discover_path(context.group)
end
override :title
def title
renderable_items.any? ? _('Security & Compliance') : _('Security')
end
override :sprite_icon
def sprite_icon
'shield'
end
override :render?
def render?
super || context.show_discover_group_security
end
override :active_routes
def active_routes
return {} if renderable_items.empty?
{ page: link }
end
private
def security_dashboard_menu_item
unless context.group.licensed_feature_available?(:security_dashboard)
return ::Sidebars::NilMenuItem.new(item_id: :security_dashboard)
end
::Sidebars::MenuItem.new(
title: _('Security Dashboard'),
link: group_security_dashboard_path(context.group),
active_routes: { path: 'dashboard#show' },
item_id: :security_dashboard
)
end
def vulnerability_report_menu_item
unless context.group.licensed_feature_available?(:security_dashboard)
return ::Sidebars::NilMenuItem.new(item_id: :vulnerability_report)
end
::Sidebars::MenuItem.new(
title: _('Vulnerability Report'),
link: group_security_vulnerabilities_path(context.group),
active_routes: { path: 'vulnerabilities#index' },
item_id: :vulnerability_report
)
end
def compliance_menu_item
unless group_level_compliance_dashboard_available?
return ::Sidebars::NilMenuItem.new(item_id: :compliance)
end
::Sidebars::MenuItem.new(
title: _('Compliance'),
link: group_security_compliance_dashboard_path(context.group),
active_routes: { path: 'compliance_dashboards#show' },
item_id: :compliance
)
end
def group_level_compliance_dashboard_available?
context.group.licensed_feature_available?(:group_level_compliance_dashboard) &&
can?(context.current_user, :read_group_compliance_dashboard, context.group)
end
def credentials_menu_item
unless group_level_credentials_inventory_available?
return ::Sidebars::NilMenuItem.new(item_id: :credentials)
end
::Sidebars::MenuItem.new(
title: _('Credentials'),
link: group_security_credentials_path(context.group),
active_routes: { path: 'credentials#index' },
item_id: :credentials
)
end
def group_level_credentials_inventory_available?
context.group.licensed_feature_available?(:credentials_inventory) &&
can?(context.current_user, :read_group_credentials_inventory, context.group) &&
context.group.enforced_group_managed_accounts?
end
def audit_events_menu_item
unless group_level_audit_events_available?
return ::Sidebars::NilMenuItem.new(item_id: :audit_events)
end
::Sidebars::MenuItem.new(
title: _('Audit Events'),
link: group_audit_events_path(context.group),
active_routes: { path: 'audit_events#index' },
item_id: :audit_events
)
end
def group_level_audit_events_available?
context.group.licensed_feature_available?(:audit_events) &&
can?(context.current_user, :read_group_audit_events, context.group)
end
end
end
end
end
......@@ -36,7 +36,7 @@ RSpec.describe 'Groups > Audit Events', :js do
end
it 'has Audit Events button in head nav bar' do
visit group_security_dashboard_path(group)
visit group_audit_events_path(group)
expect(page).to have_link('Audit Events')
end
......
......@@ -13,23 +13,6 @@ RSpec.describe Groups::SecurityFeaturesHelper do
allow(helper).to receive(:can?).and_return(false)
end
describe '#group_level_security_dashboard_available?' do
where(:security_dashboard_feature_enabled, :result) do
true | true
false | false
end
with_them do
before do
stub_licensed_features(security_dashboard: security_dashboard_feature_enabled)
end
it 'returns the expected result' do
expect(helper.group_level_security_dashboard_available?(group)).to eq(result)
end
end
end
describe '#group_level_security_dashboard_available?' do
where(:group_level_compliance_dashboard_enabled, :read_group_compliance_dashboard_permission, :result) do
false | false | false
......@@ -75,84 +58,6 @@ RSpec.describe Groups::SecurityFeaturesHelper do
end
end
describe '#group_level_audit_events_available?' do
where(:audit_events_feature_enabled, :read_group_audit_events_permission, :result) do
true | false | false
true | true | true
false | false | false
false | true | false
end
with_them do
before do
stub_licensed_features(audit_events: audit_events_feature_enabled)
allow(helper).to receive(:can?).with(user, :read_group_audit_events, group)
.and_return(read_group_audit_events_permission)
end
it 'returns the expected result' do
expect(helper.group_level_audit_events_available?(group)).to eq(result)
end
end
end
describe '#primary_group_level_security_feature_path' do
subject { helper.primary_group_level_security_feature_path(group) }
context 'group_level_security_dashboard is available' do
before do
allow(helper).to receive(:group_level_security_dashboard_available?).with(group).and_return(true)
end
it 'returns path to security dashboard' do
expect(subject).to eq(group_security_dashboard_path(group))
end
end
context 'group_level_compliance_dashboard is available' do
before do
allow(helper).to receive(:group_level_compliance_dashboard_available?).with(group).and_return(true)
end
it 'returns path to compliance dashboard' do
expect(subject).to eq(group_security_compliance_dashboard_path(group))
end
end
context 'group_level_credentials_inventory is available' do
before do
allow(helper).to receive(:group_level_credentials_inventory_available?).with(group).and_return(true)
end
it 'returns path to credentials inventory dashboard' do
expect(subject).to eq(group_security_credentials_path(group))
end
end
context 'group_level_audit_events is available' do
before do
allow(helper).to receive(:group_level_audit_events_available?).with(group).and_return(true)
end
it 'returns path to audit events' do
expect(subject).to eq(group_audit_events_path(group))
end
end
context 'when no security features are available' do
before do
allow(helper).to receive(:group_level_security_dashboard_available?).with(group).and_return(false)
allow(helper).to receive(:group_level_compliance_dashboard_available?).with(group).and_return(false)
allow(helper).to receive(:group_level_credentials_inventory_available?).with(group).and_return(false)
allow(helper).to receive(:group_level_audit_events_available?).with(group).and_return(false)
end
it 'returns nil' do
expect(subject).to be_nil
end
end
end
describe '#group_level_security_dashboard_data' do
subject { helper.group_level_security_dashboard_data(group) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Groups::Menus::SecurityComplianceMenu do
let_it_be(:owner) { create(:user) }
let_it_be_with_refind(:group) do
create(:group, :private).tap do |g|
g.add_owner(owner)
end
end
let(:user) { owner }
let(:show_group_discover_security) { false }
let(:context) { Sidebars::Groups::Context.new(current_user: user, container: group, show_discover_group_security: show_group_discover_security) }
let(:menu) { described_class.new(context) }
describe '#link' do
subject { menu.link }
context 'when menu has menu items' do
it 'returns first visible menu item link' do
expect(subject).to eq menu.renderable_items.first.link
end
end
context 'when menu does no have any menu item' do
let(:user) { nil }
it 'returns show group security page' do
expect(subject).to eq "/groups/#{group.full_path}/-/security/discover"
end
end
end
describe '#title' do
subject { menu.title }
specify do
is_expected.to eq 'Security & Compliance'
end
context 'when menu does not have any menu items' do
let(:user) { nil }
specify do
is_expected.to eq 'Security'
end
end
end
describe '#render?' do
subject { menu.render? }
it 'returns true if there are menu items' do
is_expected.to be true
end
context 'when there are no menu items' do
let(:user) { nil }
it 'returns false if there are no menu items' do
is_expected.to be false
end
context 'when show group discover security option is enabled' do
let(:show_group_discover_security) { true }
specify { is_expected.to be true }
end
end
end
describe 'Menu Items' do
subject { described_class.new(context).renderable_items.index { |e| e.item_id == item_id } }
shared_examples 'menu access rights' do
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Security Dashboard' do
let(:item_id) { :security_dashboard }
context 'when security_dashboard feature is enabled' do
before do
stub_licensed_features(security_dashboard: true)
end
specify { is_expected.not_to be_nil }
end
context 'when security_dashboard feature is not enabled' do
specify { is_expected.to be_nil }
end
end
describe 'Vulnerability Report' do
let(:item_id) { :vulnerability_report }
context 'when security_dashboard feature is enabled' do
before do
stub_licensed_features(security_dashboard: true)
end
specify { is_expected.not_to be_nil }
end
context 'when security_dashboard feature is not enabled' do
specify { is_expected.to be_nil }
end
end
describe 'Compliance' do
let(:item_id) { :compliance }
context 'when group_level_compliance_dashboard feature is enabled' do
before do
stub_licensed_features(group_level_compliance_dashboard: true)
end
it_behaves_like 'menu access rights'
end
context 'when group_level_compliance_dashboard feature is not enabled' do
specify { is_expected.to be_nil }
end
end
describe 'Credentials' do
let(:item_id) { :credentials }
context 'when credentials_inventory feature is enabled' do
before do
stub_licensed_features(credentials_inventory: true)
end
context 'when group magement is not enforced' do
specify { is_expected.to be_nil }
end
context 'when group magement is enforced' do
before do
allow(group).to receive(:enforced_group_managed_accounts?).and_return(true)
end
it_behaves_like 'menu access rights'
end
end
context 'when credentials_inventory feature is not enabled' do
specify { is_expected.to be_nil }
end
end
describe 'Audit Events' do
let(:item_id) { :audit_events }
context 'when audit_events feature is enabled' do
before do
stub_licensed_features(audit_events: true)
end
it_behaves_like 'menu access rights'
end
context 'when audit_events feature is not enabled' do
before do
stub_licensed_features(audit_events: false)
end
specify { is_expected.to be_nil }
end
end
end
end
......@@ -175,128 +175,7 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end
end
describe 'DevOps adoption link' do
let!(:current_user) { create(:user) }
before do
group.add_maintainer(current_user)
allow(view).to receive(:current_user).and_return(current_user)
end
context 'DevOps adoption feature is available' do
before do
stub_licensed_features(group_level_devops_adoption: true)
end
it 'is visible' do
render
expect(rendered).to have_text 'DevOps adoption'
end
end
context 'DevOps adoption feature is not available' do
before do
stub_licensed_features(group_level_devops_adoption: false)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'DevOps adoption'
end
end
end
describe 'contribution analytics tab' do
let!(:current_user) { create(:user) }
before do
group.add_guest(current_user)
allow(view).to receive(:current_user).and_return(current_user)
end
context 'contribution analytics feature is available' do
before do
stub_licensed_features(contribution_analytics: true)
end
it 'is visible' do
render
expect(rendered).to have_text 'Contribution'
end
end
context 'contribution analytics feature is not available' do
before do
stub_licensed_features(contribution_analytics: false)
end
context 'we do not show promotions' do
before do
allow(LicenseHelper).to receive(:show_promotions?).and_return(false)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'Contribution'
end
end
end
context 'no license installed' do
before do
allow(License).to receive(:current).and_return(nil)
stub_application_setting(check_namespace_plan: false)
allow(view).to receive(:can?) { |*args| Ability.allowed?(*args) }
end
it 'is visible when there is no valid license but we show promotions' do
stub_licensed_features(contribution_analytics: false)
render
expect(rendered).to have_text 'Contribution'
end
end
it 'is visible' do
stub_licensed_features(contribution_analytics: true)
render
expect(rendered).to have_text 'Contribution'
end
describe 'group issue boards link' do
context 'when multiple issue board is disabled' do
it 'shows link text in singular' do
render
expect(rendered).to have_text 'Board'
end
end
context 'when multiple issue board is enabled' do
before do
stub_licensed_features(multiple_group_issue_boards: true)
end
it 'shows link text in plural' do
render
expect(rendered).to have_text 'Boards'
end
end
end
end
describe 'security dashboard tab' do
describe 'Security & Compliance menu' do
let(:group) { create(:group_with_plan, plan: :ultimate_plan) }
before do
......@@ -433,6 +312,127 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end
end
describe 'DevOps adoption link' do
let!(:current_user) { create(:user) }
before do
group.add_maintainer(current_user)
allow(view).to receive(:current_user).and_return(current_user)
end
context 'DevOps adoption feature is available' do
before do
stub_licensed_features(group_level_devops_adoption: true)
end
it 'is visible' do
render
expect(rendered).to have_text 'DevOps adoption'
end
end
context 'DevOps adoption feature is not available' do
before do
stub_licensed_features(group_level_devops_adoption: false)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'DevOps adoption'
end
end
end
describe 'contribution analytics tab' do
let!(:current_user) { create(:user) }
before do
group.add_guest(current_user)
allow(view).to receive(:current_user).and_return(current_user)
end
context 'contribution analytics feature is available' do
before do
stub_licensed_features(contribution_analytics: true)
end
it 'is visible' do
render
expect(rendered).to have_text 'Contribution'
end
end
context 'contribution analytics feature is not available' do
before do
stub_licensed_features(contribution_analytics: false)
end
context 'we do not show promotions' do
before do
allow(LicenseHelper).to receive(:show_promotions?).and_return(false)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'Contribution'
end
end
end
context 'no license installed' do
before do
allow(License).to receive(:current).and_return(nil)
stub_application_setting(check_namespace_plan: false)
allow(view).to receive(:can?) { |*args| Ability.allowed?(*args) }
end
it 'is visible when there is no valid license but we show promotions' do
stub_licensed_features(contribution_analytics: false)
render
expect(rendered).to have_text 'Contribution'
end
end
it 'is visible' do
stub_licensed_features(contribution_analytics: true)
render
expect(rendered).to have_text 'Contribution'
end
describe 'group issue boards link' do
context 'when multiple issue board is disabled' do
it 'shows link text in singular' do
render
expect(rendered).to have_text 'Board'
end
end
context 'when multiple issue board is enabled' do
before do
stub_licensed_features(multiple_group_issue_boards: true)
end
it 'shows link text in plural' do
render
expect(rendered).to have_text 'Boards'
end
end
end
end
describe 'wiki tab' do
let(:can_read_wiki) { true }
......
......@@ -136,7 +136,6 @@ module Gitlab
def self.process_name
return 'sidekiq' if Gitlab::Runtime.sidekiq?
return 'action_cable' if Gitlab::Runtime.action_cable?
return 'console' if Gitlab::Runtime.console?
return 'test' if Rails.env.test?
......
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Chain
# This will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/337167
class LegacySkip < Chain::Skip
include ::Gitlab::Utils::StrongMemoize
SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i.freeze
def perform!
return if ::Feature.enabled?(:ci_skip_before_parsing_yaml, project, default_enabled: :yaml)
if skipped?
if @command.save_incompleted
# Project iid must be called outside a transaction, so we ensure it is set here
# otherwise it may be set within the state transition transaction of the skip call
# which it will lock the InternalId row for the whole transaction
@pipeline.ensure_project_iid!
@pipeline.skip
end
end
end
def break?
return if ::Feature.enabled?(:ci_skip_before_parsing_yaml, project, default_enabled: :yaml)
skipped?
end
private
def skipped?
!@command.ignore_skip_ci && (commit_message_skips_ci? || push_option_skips_ci?)
end
def commit_message_skips_ci?
return false unless @pipeline.git_commit_message
strong_memoize(:commit_message_skips_ci) do
!!(@pipeline.git_commit_message =~ SKIP_PATTERN)
end
end
def push_option_skips_ci?
@command.push_options.present? &&
@command.push_options.deep_symbolize_keys.dig(:ci, :skip).present?
end
end
end
end
end
end
......@@ -10,6 +10,8 @@ module Gitlab
SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i.freeze
def perform!
return unless ::Feature.enabled?(:ci_skip_before_parsing_yaml, project, default_enabled: :yaml)
if skipped?
if @command.save_incompleted
# Project iid must be called outside a transaction, so we ensure it is set here
......@@ -22,16 +24,18 @@ module Gitlab
end
end
def skipped?
!@command.ignore_skip_ci && (commit_message_skips_ci? || push_option_skips_ci?)
end
def break?
return unless ::Feature.enabled?(:ci_skip_before_parsing_yaml, project, default_enabled: :yaml)
skipped?
end
private
def skipped?
!@command.ignore_skip_ci && (commit_message_skips_ci? || push_option_skips_ci?)
end
def commit_message_skips_ci?
return false unless @pipeline.git_commit_message
......
......@@ -31828,6 +31828,9 @@ msgstr ""
msgid "Summary"
msgstr ""
msgid "Summary / Note"
msgstr ""
msgid "Sunday"
msgstr ""
......
......@@ -33,14 +33,6 @@ module QA
element :billing_link
end
view 'ee/app/views/layouts/nav/ee/_security_link.html.haml' do
element :security_compliance_link
element :group_secure_submenu
element :security_dashboard_link
element :vulnerability_report_link
element :audit_events_settings_link
end
view 'ee/app/views/layouts/nav/_group_insights_link.html.haml' do
element :group_insights_link
end
......@@ -53,14 +45,6 @@ module QA
end
end
def go_to_audit_events_settings
hover_element(:security_compliance_link) do
within_submenu(:group_secure_submenu) do
click_element(:audit_events_settings_link)
end
end
end
def go_to_issue_boards
hover_issues do
within_submenu do
......@@ -116,17 +100,25 @@ module QA
end
def click_group_security_link
hover_element(:security_compliance_link) do
within_submenu(:group_secure_submenu) do
click_element(:security_dashboard_link)
hover_security_and_compliance do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Security Dashboard')
end
end
end
def click_group_vulnerability_link
hover_element(:security_compliance_link) do
within_submenu(:group_secure_submenu) do
click_element(:vulnerability_report_link)
hover_security_and_compliance do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Vulnerability Report')
end
end
end
def go_to_audit_events
hover_security_and_compliance do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Audit Events')
end
end
end
......@@ -152,6 +144,17 @@ module QA
end
end
end
private
def hover_security_and_compliance
within_sidebar do
scroll_to_element(:sidebar_menu_link, menu_item: 'Security & Compliance')
find_element(:sidebar_menu_link, menu_item: 'Security & Compliance').hover
yield
end
end
end
end
end
......
......@@ -11,7 +11,7 @@ module QA
it 'logs audit events for UI operations' do
wait_for_audit_events(expected_events, group)
Page::Group::Menu.perform(&:go_to_audit_events_settings)
Page::Group::Menu.perform(&:go_to_audit_events)
expected_events.each do |expected_event|
# Sometimes the audit logs are not displayed in the UI
# right away so a refresh may be needed.
......
......@@ -5,7 +5,7 @@ module QA
RSpec.describe 'Manage' do
shared_examples 'audit event' do |expected_events|
it 'logs audit events for UI operations' do
Page::Group::Menu.perform(&:go_to_audit_events_settings)
Page::Group::Menu.perform(&:go_to_audit_events)
expected_events.each do |expected_event|
expect(page).to have_text(expected_event)
end
......
......@@ -16,9 +16,10 @@ export const getIssueTimelogsQueryResponse = {
},
spentAt: '2020-05-01T00:00:00Z',
note: {
body: 'I paired with @root on this last week.',
body: 'A note',
__typename: 'Note',
},
summary: 'A summary',
},
{
__typename: 'Timelog',
......@@ -29,6 +30,7 @@ export const getIssueTimelogsQueryResponse = {
},
spentAt: '2021-05-07T13:19:01Z',
note: null,
summary: 'A summary',
},
{
__typename: 'Timelog',
......@@ -39,9 +41,10 @@ export const getIssueTimelogsQueryResponse = {
},
spentAt: '2021-05-01T00:00:00Z',
note: {
body: 'I did some work on this last week.',
body: 'A note',
__typename: 'Note',
},
summary: null,
},
],
__typename: 'TimelogConnection',
......@@ -70,6 +73,7 @@ export const getMrTimelogsQueryResponse = {
body: 'Thirty minutes!',
__typename: 'Note',
},
summary: null,
},
{
__typename: 'Timelog',
......@@ -80,6 +84,7 @@ export const getMrTimelogsQueryResponse = {
},
spentAt: '2021-05-07T14:44:39Z',
note: null,
summary: null,
},
{
__typename: 'Timelog',
......@@ -93,6 +98,7 @@ export const getMrTimelogsQueryResponse = {
body: 'A note with some time',
__typename: 'Note',
},
summary: null,
},
],
__typename: 'TimelogConnection',
......
......@@ -74,6 +74,8 @@ describe('Issuable Time Tracking Report', () => {
expect(getAllByRole(wrapper.element, 'row', { name: /John Doe18/i })).toHaveLength(1);
expect(getAllByRole(wrapper.element, 'row', { name: /Administrator/i })).toHaveLength(2);
expect(getAllByRole(wrapper.element, 'row', { name: /A note/i })).toHaveLength(1);
expect(getAllByRole(wrapper.element, 'row', { name: /A summary/i })).toHaveLength(2);
});
});
......
......@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['Timelog'] do
let(:fields) { %i[spent_at time_spent user issue merge_request note] }
let(:fields) { %i[spent_at time_spent user issue merge_request note summary] }
it { expect(described_class.graphql_name).to eq('Timelog') }
it { expect(described_class).to have_graphql_fields(fields) }
......
......@@ -749,4 +749,44 @@ RSpec.describe Member do
end
end
end
describe 'log_invitation_token_cleanup' do
let_it_be(:project) { create :project }
context 'when on gitlab.com' do
before do
allow(Gitlab).to receive(:com?).and_return true
end
it "doesn't log info for members without invitation or accepted invitation" do
expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
create :project_member
create :project_member, :invited, invite_accepted_at: nil
create :project_member, invite_token: nil, invite_accepted_at: Time.zone.now
end
it 'logs error for accepted members with token and creates membership' do
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(kind_of(StandardError), kind_of(Hash))
expect do
create :project_member, :invited, source: project, invite_accepted_at: Time.zone.now
end.to change { Member.count }.by(1)
end
end
context 'when not on gitlab.com' do
before do
allow(Gitlab).to receive(:com?).and_return false
end
it 'does not log error for accepted members with token and creates membership' do
expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
expect do
create :project_member, :invited, source: project, invite_accepted_at: Time.zone.now
end.to change { Member.count }.by(1)
end
end
end
end
......@@ -1542,8 +1542,8 @@ RSpec.describe Note do
describe '#post_processed_cache_key' do
let(:note) { build(:note) }
it 'returns cache key by default' do
expect(note.post_processed_cache_key).to eq(note.cache_key)
it 'returns cache key and author cache key by default' do
expect(note.post_processed_cache_key).to eq("#{note.cache_key}:#{note.author.cache_key}")
end
context 'when note has redacted_note_html' do
......@@ -1554,7 +1554,7 @@ RSpec.describe Note do
end
it 'returns cache key with redacted_note_html sha' do
expect(note.post_processed_cache_key).to eq("#{note.cache_key}:#{Digest::SHA1.hexdigest(redacted_note_html)}")
expect(note.post_processed_cache_key).to eq("#{note.cache_key}:#{note.author.cache_key}:#{Digest::SHA1.hexdigest(redacted_note_html)}")
end
end
end
......
......@@ -55,7 +55,8 @@ RSpec.describe 'merge requests discussions' do
context 'caching', :use_clean_rails_memory_store_caching do
let(:reference) { create(:issue, project: project) }
let!(:first_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, note: "reference: #{reference.to_reference}") }
let(:author) { create(:user) }
let!(:first_note) { create(:diff_note_on_merge_request, author: author, noteable: merge_request, project: project, note: "reference: #{reference.to_reference}") }
let!(:second_note) { create(:diff_note_on_merge_request, in_reply_to: first_note, noteable: merge_request, project: project) }
let!(:award_emoji) { create(:award_emoji, awardable: first_note) }
......@@ -181,6 +182,26 @@ RSpec.describe 'merge requests discussions' do
end
end
context 'when author detail changes' do
before do
author.update!(name: "#{author.name} (Updated)")
end
it_behaves_like 'cache miss' do
let(:changed_notes) { [first_note, second_note] }
end
end
context 'when author status changes' do
before do
Users::SetStatusService.new(author, message: "updated status").execute
end
it_behaves_like 'cache miss' do
let(:changed_notes) { [first_note, second_note] }
end
end
context 'when merge_request_discussion_cache is disabled' do
before do
stub_feature_flags(merge_request_discussion_cache: false)
......
......@@ -19,7 +19,6 @@ RSpec.describe Ci::CreatePipelineService do
def execute_service(
source: :push,
after: project.commit.id,
message: 'Message',
ref: ref_name,
trigger_request: nil,
variables_attributes: nil,
......@@ -32,7 +31,6 @@ RSpec.describe Ci::CreatePipelineService do
params = { ref: ref,
before: '00000000',
after: after,
commits: [{ message: message }],
variables_attributes: variables_attributes,
push_options: push_options,
source_sha: source_sha,
......@@ -508,7 +506,7 @@ RSpec.describe Ci::CreatePipelineService do
it 'creates failed pipeline' do
stub_ci_pipeline_yaml_file(ci_yaml)
pipeline = execute_service(message: message).payload
pipeline = execute_service.payload
expect(pipeline).to be_persisted
expect(pipeline.builds.any?).to be false
......@@ -671,9 +669,30 @@ RSpec.describe Ci::CreatePipelineService do
end
context 'when commit contains a [ci skip] directive' do
let(:message) { "some message[ci skip]" }
shared_examples 'creating a pipeline' do
it 'does not skip pipeline creation' do
pipeline = execute_service.payload
expect(pipeline).to be_persisted
expect(pipeline.builds.first.name).to eq("rspec")
end
end
shared_examples 'skipping a pipeline' do
it 'skips pipeline creation' do
pipeline = execute_service.payload
ci_messages = [
expect(pipeline).to be_persisted
expect(pipeline.builds.any?).to be false
expect(pipeline.status).to eq("skipped")
end
end
before do
allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
end
skip_commit_messages = [
"some message[ci skip]",
"some message[skip ci]",
"some message[CI SKIP]",
......@@ -684,28 +703,19 @@ RSpec.describe Ci::CreatePipelineService do
"some message[skip-ci]"
]
before do
allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
end
skip_commit_messages.each do |skip_commit_message|
context "when the commit message is #{skip_commit_message}" do
let(:commit_message) { skip_commit_message }
ci_messages.each do |ci_message|
it "skips builds creation if the commit message is #{ci_message}" do
pipeline = execute_service(message: ci_message).payload
it_behaves_like 'skipping a pipeline'
expect(pipeline).to be_persisted
expect(pipeline.builds.any?).to be false
expect(pipeline.status).to eq("skipped")
end
end
shared_examples 'creating a pipeline' do
it 'does not skip pipeline creation' do
allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { commit_message }
pipeline = execute_service(message: commit_message).payload
context 'when the FF ci_skip_before_parsing_yaml is disabled' do
before do
stub_feature_flags(ci_skip_before_parsing_yaml: false)
end
expect(pipeline).to be_persisted
expect(pipeline.builds.first.name).to eq("rspec")
it_behaves_like 'skipping a pipeline'
end
end
end
......@@ -713,6 +723,14 @@ RSpec.describe Ci::CreatePipelineService do
let(:commit_message) { 'some message' }
it_behaves_like 'creating a pipeline'
context 'when the FF ci_skip_before_parsing_yaml is disabled' do
before do
stub_feature_flags(ci_skip_before_parsing_yaml: false)
end
it_behaves_like 'creating a pipeline'
end
end
context 'when commit message is nil' do
......@@ -722,9 +740,27 @@ RSpec.describe Ci::CreatePipelineService do
end
context 'when there is [ci skip] tag in commit message and yaml is invalid' do
let(:commit_message) { 'some message [ci skip]' }
let(:ci_yaml) { 'invalid: file: fiile' }
it_behaves_like 'a failed pipeline'
before do
stub_ci_pipeline_yaml_file(ci_yaml)
end
it_behaves_like 'skipping a pipeline'
context 'when the FF ci_skip_before_parsing_yaml is disabled' do
before do
stub_feature_flags(ci_skip_before_parsing_yaml: false)
end
it 'creates the pipeline with error' do
pipeline = execute_service.payload
expect(pipeline).to be_persisted
expect(pipeline.status).to eq("failed")
end
end
end
end
......
......@@ -8,6 +8,18 @@ RSpec.describe Users::SetStatusService do
subject(:service) { described_class.new(current_user, params) }
describe '#execute' do
shared_examples_for 'bumps user' do
it 'bumps User#updated_at' do
expect { service.execute }.to change { current_user.updated_at }
end
end
shared_examples_for 'does not bump user' do
it 'does not bump User#updated_at' do
expect { service.execute }.not_to change { current_user.updated_at }
end
end
context 'when params are set' do
let(:params) { { emoji: 'taurus', message: 'a random status', availability: 'busy' } }
......@@ -31,6 +43,8 @@ RSpec.describe Users::SetStatusService do
expect(service.execute).to be(true)
end
it_behaves_like 'bumps user'
context 'when setting availability to not_set' do
before do
params[:availability] = 'not_set'
......@@ -72,6 +86,8 @@ RSpec.describe Users::SetStatusService do
it 'does not update the status if the current user is not allowed' do
expect { service.execute }.not_to change { target_user.status }
end
it_behaves_like 'does not bump user'
end
end
......@@ -79,20 +95,28 @@ RSpec.describe Users::SetStatusService do
let(:params) { {} }
shared_examples 'removes user status record' do
it 'deletes the status' do
status = create(:user_status, user: current_user)
it 'deletes the user status record' do
expect { service.execute }
.to change { current_user.reload.status }.from(status).to(nil)
.to change { current_user.reload.status }.from(user_status).to(nil)
end
end
it_behaves_like 'removes user status record'
it_behaves_like 'bumps user'
end
context 'when not_set is given for availability' do
let(:params) { { availability: 'not_set' } }
context 'when user has existing user status record' do
let!(:user_status) { create(:user_status, user: current_user) }
it_behaves_like 'removes user status record'
context 'when not_set is given for availability' do
let(:params) { { availability: 'not_set' } }
it_behaves_like 'removes user status record'
end
end
context 'when user has no existing user status record' do
it_behaves_like 'does not bump user'
end
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment