Commit 21e5b701 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 894555b6 8b96dd43
...@@ -107,7 +107,7 @@ export default { ...@@ -107,7 +107,7 @@ export default {
v-if="!popoverDismissed" v-if="!popoverDismissed"
show show
:target="target" :target="target"
placement="rightbottom" placement="right"
trigger="manual" trigger="manual"
container="viewport" container="viewport"
:css-classes="['suggest-gitlab-ci-yml', 'ml-4']" :css-classes="['suggest-gitlab-ci-yml', 'ml-4']"
......
import $ from 'jquery'; import $ from 'jquery';
import ContextualSidebar from './contextual_sidebar'; import ContextualSidebar from './contextual_sidebar';
import initFlyOutNav from './fly_out_nav'; import initFlyOutNav from './fly_out_nav';
import { setNotification } from './whats_new/utils/notification';
function hideEndFade($scrollingTabs) { function hideEndFade($scrollingTabs) {
$scrollingTabs.each(function scrollTabsLoop() { $scrollingTabs.each(function scrollTabsLoop() {
...@@ -14,25 +15,17 @@ function hideEndFade($scrollingTabs) { ...@@ -14,25 +15,17 @@ function hideEndFade($scrollingTabs) {
function initDeferred() { function initDeferred() {
$(document).trigger('init.scrolling-tabs'); $(document).trigger('init.scrolling-tabs');
const whatsNewTriggerEl = document.querySelector('.js-whats-new-trigger'); const appEl = document.getElementById('whats-new-app');
if (whatsNewTriggerEl) { if (!appEl) return;
const storageKey = whatsNewTriggerEl.getAttribute('data-storage-key');
$('.header-help').on('show.bs.dropdown', () => { setNotification(appEl);
const displayNotification = JSON.parse(localStorage.getItem(storageKey)); document.querySelector('.js-whats-new-trigger').addEventListener('click', () => {
if (displayNotification === false) { import(/* webpackChunkName: 'whatsNewApp' */ '~/whats_new')
$('.js-whats-new-notification-count').remove(); .then(({ default: initWhatsNew }) => {
} initWhatsNew(appEl);
}); })
.catch(() => {});
whatsNewTriggerEl.addEventListener('click', () => { });
import(/* webpackChunkName: 'whatsNewApp' */ '~/whats_new')
.then(({ default: initWhatsNew }) => {
initWhatsNew();
})
.catch(() => {});
});
}
} }
export default function initLayoutNav() { export default function initLayoutNav() {
......
import Vue from 'vue'; import Vue from 'vue';
import { mapState } from 'vuex';
import App from './components/app.vue'; import App from './components/app.vue';
import store from './store'; import store from './store';
import { getStorageKey, setNotification } from './utils/notification';
let whatsNewApp; let whatsNewApp;
export default () => { export default el => {
if (whatsNewApp) { if (whatsNewApp) {
store.dispatch('openDrawer'); store.dispatch('openDrawer');
} else { } else {
const whatsNewElm = document.getElementById('whats-new-app'); const storageKey = getStorageKey(el);
whatsNewApp = new Vue({ whatsNewApp = new Vue({
el: whatsNewElm, el,
store, store,
components: { components: {
App, App,
}, },
computed: {
...mapState(['open']),
},
watch: {
open() {
setNotification(el);
},
},
render(createElement) { render(createElement) {
return createElement('app', { return createElement('app', {
props: { props: { storageKey },
storageKey: whatsNewElm.getAttribute('data-storage-key'),
},
}); });
}, },
}); });
......
export const getStorageKey = appEl => appEl.getAttribute('data-storage-key');
export const setNotification = appEl => {
const storageKey = getStorageKey(appEl);
const notificationEl = document.querySelector('.header-help');
let notificationCountEl = notificationEl.querySelector('.js-whats-new-notification-count');
if (JSON.parse(localStorage.getItem(storageKey)) === false) {
notificationEl.classList.remove('with-notifications');
if (notificationCountEl) {
notificationCountEl.parentElement.removeChild(notificationCountEl);
notificationCountEl = null;
}
} else {
notificationEl.classList.add('with-notifications');
}
};
...@@ -103,7 +103,8 @@ ...@@ -103,7 +103,8 @@
@include transition(color); @include transition(color);
} }
a { a,
.notification-dot {
@include transition(background-color, color, border); @include transition(background-color, color, border);
} }
......
...@@ -556,12 +556,17 @@ ...@@ -556,12 +556,17 @@
border: 1px solid $gray-normal; border: 1px solid $gray-normal;
} }
.header-user-notification-dot { .notification-dot {
background-color: $orange-300; background-color: $orange-300;
height: 12px; height: 12px;
width: 12px; width: 12px;
right: 8px; margin-top: -15px;
top: -8px; pointer-events: none;
visibility: hidden;
}
.with-notifications .notification-dot {
visibility: visible;
} }
.with-performance-bar .navbar-gitlab { .with-performance-bar .navbar-gitlab {
......
...@@ -64,14 +64,20 @@ ...@@ -64,14 +64,20 @@
color: $search-and-nav-links; color: $search-and-nav-links;
> a { > a {
.notification-dot {
border: 2px solid $nav-svg-color;
}
&.header-help-dropdown-toggle {
.notification-dot {
background-color: $search-and-nav-links;
}
}
&.header-user-dropdown-toggle { &.header-user-dropdown-toggle {
.header-user-avatar { .header-user-avatar {
border-color: $search-and-nav-links; border-color: $search-and-nav-links;
} }
.header-user-notification-dot {
border: 2px solid $nav-svg-color;
}
} }
&:hover, &:hover,
...@@ -84,9 +90,14 @@ ...@@ -84,9 +90,14 @@
fill: currentColor; fill: currentColor;
} }
&.header-user-dropdown-toggle .header-user-notification-dot { .notification-dot {
will-change: border-color, background-color;
border-color: $nav-svg-color + 33; border-color: $nav-svg-color + 33;
} }
&.header-help-dropdown-toggle .notification-dot {
background-color: $white;
}
} }
} }
...@@ -101,9 +112,15 @@ ...@@ -101,9 +112,15 @@
} }
} }
&.header-user-dropdown-toggle .header-user-notification-dot { .notification-dot {
border-color: $white; border-color: $white;
} }
&.header-help-dropdown-toggle {
.notification-dot {
background-color: $nav-svg-color;
}
}
} }
.impersonated-user, .impersonated-user,
......
...@@ -80,6 +80,8 @@ module Repositories ...@@ -80,6 +80,8 @@ module Repositories
return if Gitlab::Database.read_only? return if Gitlab::Database.read_only?
return unless repo_type.project? return unless repo_type.project?
OnboardingProgressService.new(project.namespace).execute(action: :git_read)
if Feature.enabled?(:project_statistics_sync, project, default_enabled: true) if Feature.enabled?(:project_statistics_sync, project, default_enabled: true)
Projects::FetchStatisticsIncrementService.new(project).execute Projects::FetchStatisticsIncrementService.new(project).execute
else else
......
...@@ -7,7 +7,8 @@ class NamespaceOnboardingAction < ApplicationRecord ...@@ -7,7 +7,8 @@ class NamespaceOnboardingAction < ApplicationRecord
ACTIONS = { ACTIONS = {
subscription_created: 1, subscription_created: 1,
git_write: 2 git_write: 2,
git_read: 4
}.freeze }.freeze
enum action: ACTIONS enum action: ACTIONS
......
# frozen_string_literal: true
class OnboardingProgressService
def initialize(namespace)
@namespace = namespace
end
def execute(action:)
NamespaceOnboardingAction.create_action(@namespace, action)
end
end
...@@ -74,6 +74,7 @@ ...@@ -74,6 +74,7 @@
%span.gl-sr-only %span.gl-sr-only
= s_('Nav|Help') = s_('Nav|Help')
= sprite_icon('question') = sprite_icon('question')
%span.notification-dot.rounded-circle.gl-absolute
= sprite_icon('chevron-down', css_class: 'caret-down') = sprite_icon('chevron-down', css_class: 'caret-down')
.dropdown-menu.dropdown-menu-right .dropdown-menu.dropdown-menu-right
= render 'layouts/header/help_dropdown' = render 'layouts/header/help_dropdown'
......
---
title: Add new column `finding_uuid` into `vulnerability_feedback` table
merge_request: 48923
author:
type: changed
# frozen_string_literal: true
class AddFindingUuidToVulnerabilityFeedback < ActiveRecord::Migration[6.0]
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
add_column :vulnerability_feedback, :finding_uuid, :uuid
end
end
cc978ac56ed177575706436c52125b51915dff97a20ed47ae0c7b16caa837313
\ No newline at end of file
...@@ -17377,7 +17377,8 @@ CREATE TABLE vulnerability_feedback ( ...@@ -17377,7 +17377,8 @@ CREATE TABLE vulnerability_feedback (
merge_request_id integer, merge_request_id integer,
comment_author_id integer, comment_author_id integer,
comment text, comment text,
comment_timestamp timestamp with time zone comment_timestamp timestamp with time zone,
finding_uuid uuid
); );
CREATE SEQUENCE vulnerability_feedback_id_seq CREATE SEQUENCE vulnerability_feedback_id_seq
......
...@@ -6,8 +6,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -6,8 +6,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Requirements for Auto DevOps # Requirements for Auto DevOps
You can set up Auto DevOps for [Kubernetes](#auto-devops-requirements-for-kubernetes) You can set up Auto DevOps for [Kubernetes](#auto-devops-requirements-for-kubernetes),
or [Amazon Elastic Container Service (ECS)](#auto-devops-requirements-for-amazon-ecs). [Amazon Elastic Container Service (ECS)](#auto-devops-requirements-for-amazon-ecs),
or [Amazon Cloud Compute](#auto-devops-requirements-for-amazon-ecs).
For more information about Auto DevOps, see [the main Auto DevOps page](index.md) For more information about Auto DevOps, see [the main Auto DevOps page](index.md)
or the [quick start guide](quick_start_guide.md). or the [quick start guide](quick_start_guide.md).
...@@ -140,3 +141,14 @@ it on its own. This template is designed to be used with Auto DevOps only. It ma ...@@ -140,3 +141,14 @@ it on its own. This template is designed to be used with Auto DevOps only. It ma
unexpectedly causing your pipeline to fail if included on its own. Also, the job unexpectedly causing your pipeline to fail if included on its own. Also, the job
names within this template may also change. Do not override these jobs' names in your names within this template may also change. Do not override these jobs' names in your
own pipeline, as the override stops working when the name changes. own pipeline, as the override stops working when the name changes.
## Auto DevOps requirements for Amazon EC2
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216008) in GitLab 13.6.
You can target [AWS EC2](../../ci/cloud_deployment/index.md)
as a deployment platform instead of Kubernetes. To use Auto DevOps with AWS EC2, you must add a
specific environment variable.
For more details, see [Custom build job for Auto DevOps](../../ci/cloud_deployment/index.md#custom-build-job-for-auto-devops)
for deployments to AWS EC2.
...@@ -190,12 +190,7 @@ export default { ...@@ -190,12 +190,7 @@ export default {
</div> </div>
</div> </div>
</a> </a>
<gl-popover <gl-popover :target="generateKey(epic)" :title="epic.title" triggers="hover" placement="left">
:target="generateKey(epic)"
:title="epic.title"
triggers="hover"
placement="lefttop"
>
<p class="text-secondary m-0">{{ timeframeString(epic) }}</p> <p class="text-secondary m-0">{{ timeframeString(epic) }}</p>
<p class="m-0">{{ popoverWeightText }}</p> <p class="m-0">{{ popoverWeightText }}</p>
</gl-popover> </gl-popover>
......
...@@ -162,7 +162,7 @@ export default { ...@@ -162,7 +162,7 @@ export default {
<gl-popover <gl-popover
:target="`milestone-item-${milestone.id}`" :target="`milestone-item-${milestone.id}`"
boundary="viewport" boundary="viewport"
placement="lefttop" placement="left"
triggers="hover" triggers="hover"
:title="milestone.title" :title="milestone.title"
> >
......
- return unless show_pipeline_minutes_notification_dot?(project, namespace) - return unless show_pipeline_minutes_notification_dot?(project, namespace)
%span.header-user-notification-dot.rounded-circle.position-relative{ data: { track_label: "show_buy_ci_minutes_notification", track_property: current_user.namespace.actual_plan_name, track_event: 'render' } } %span.notification-dot.rounded-circle.gl-absolute.gl-visibility-visible{ data: { track_label: "show_buy_ci_minutes_notification", track_property: current_user.namespace.actual_plan_name, track_event: 'render' } }
---
name: query_cache_for_load_balancing
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46765
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/276203
milestone: '13.7'
type: development
group: group::memory
default_enabled: false
...@@ -7,7 +7,7 @@ module Gitlab ...@@ -7,7 +7,7 @@ module Gitlab
class Host class Host
attr_reader :pool, :last_checked_at, :intervals, :load_balancer, :host, :port attr_reader :pool, :last_checked_at, :intervals, :load_balancer, :host, :port
delegate :connection, :release_connection, to: :pool delegate :connection, :release_connection, :enable_query_cache!, :disable_query_cache!, to: :pool
CONNECTION_ERRORS = CONNECTION_ERRORS =
if defined?(PG) if defined?(PG)
......
...@@ -12,6 +12,7 @@ module Gitlab ...@@ -12,6 +12,7 @@ module Gitlab
# always returns a connection to the primary. # always returns a connection to the primary.
class LoadBalancer class LoadBalancer
CACHE_KEY = :gitlab_load_balancer_host CACHE_KEY = :gitlab_load_balancer_host
ENSURE_CACHING_KEY = 'ensure_caching'
attr_reader :host_list attr_reader :host_list
...@@ -28,6 +29,8 @@ module Gitlab ...@@ -28,6 +29,8 @@ module Gitlab
conflict_retried = 0 conflict_retried = 0
while host while host
ensure_caching!
begin begin
return yield host.connection return yield host.connection
rescue => error rescue => error
...@@ -95,7 +98,12 @@ module Gitlab ...@@ -95,7 +98,12 @@ module Gitlab
# Releases the host and connection for the current thread. # Releases the host and connection for the current thread.
def release_host def release_host
RequestStore[CACHE_KEY]&.release_connection if host = RequestStore[CACHE_KEY]
host.disable_query_cache!
host.release_connection
end
RequestStore.delete(ENSURE_CACHING_KEY)
RequestStore.delete(CACHE_KEY) RequestStore.delete(CACHE_KEY)
end end
...@@ -169,6 +177,23 @@ module Gitlab ...@@ -169,6 +177,23 @@ module Gitlab
error.is_a?(PG::TRSerializationFailure) error.is_a?(PG::TRSerializationFailure)
end end
end end
private
# TODO:
# Move enable_query_cache! to ConnectionPool (https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database.rb#L223)
# when the feature flag is removed in https://gitlab.com/gitlab-org/gitlab/-/issues/276203.
def ensure_caching!
# Feature (Flipper gem) reads the data from the database, and it would cause the infinite loop here.
# We need to ensure that the code below is executed only once, until the feature flag is removed.
return if RequestStore[ENSURE_CACHING_KEY]
RequestStore[ENSURE_CACHING_KEY] = true
if Feature.enabled?(:query_cache_for_load_balancing)
host.enable_query_cache!
end
end
end end
end end
end end
......
...@@ -11,12 +11,14 @@ RSpec.describe "renders a `whats new` dropdown item", :js do ...@@ -11,12 +11,14 @@ RSpec.describe "renders a `whats new` dropdown item", :js do
sign_in(user) sign_in(user)
end end
it 'shows notification count and removes it once viewed' do it 'shows notification dot and count and removes it once viewed' do
visit root_dashboard_path visit root_dashboard_path
find('.header-help-dropdown-toggle').click
page.within '.header-help' do page.within '.header-help' do
expect(page).to have_selector('.notification-dot', visible: true)
find('.header-help-dropdown-toggle').click
expect(page).to have_button(text: "See what's new at GitLab") expect(page).to have_button(text: "See what's new at GitLab")
expect(page).to have_selector('.js-whats-new-notification-count') expect(page).to have_selector('.js-whats-new-notification-count')
...@@ -27,6 +29,7 @@ RSpec.describe "renders a `whats new` dropdown item", :js do ...@@ -27,6 +29,7 @@ RSpec.describe "renders a `whats new` dropdown item", :js do
find('.header-help-dropdown-toggle').click find('.header-help-dropdown-toggle').click
page.within '.header-help' do page.within '.header-help' do
expect(page).not_to have_selector('.notification-dot', visible: true)
expect(page).to have_button(text: "See what's new at GitLab") expect(page).to have_button(text: "See what's new at GitLab")
expect(page).not_to have_selector('.js-whats-new-notification-count') expect(page).not_to have_selector('.js-whats-new-notification-count')
end end
......
...@@ -2,16 +2,15 @@ ...@@ -2,16 +2,15 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer do RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
let(:pool_spec) { ActiveRecord::Base.connection_pool.spec }
let(:pool) { ActiveRecord::ConnectionAdapters::ConnectionPool.new(pool_spec) }
let(:lb) { described_class.new(%w(localhost localhost)) } let(:lb) { described_class.new(%w(localhost localhost)) }
before do before do
allow(Gitlab::Database).to receive(:create_connection_pool) allow(Gitlab::Database).to receive(:create_connection_pool)
.and_return(ActiveRecord::Base.connection_pool) .and_return(pool)
end
after do
RequestStore.delete(described_class::CACHE_KEY)
end end
def raise_and_wrap(wrapper, original) def raise_and_wrap(wrapper, original)
...@@ -52,8 +51,28 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer do ...@@ -52,8 +51,28 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer do
allow(lb).to receive(:host).and_return(host) allow(lb).to receive(:host).and_return(host)
expect(host).to receive(:connection).and_return(connection) expect(host).to receive(:connection).and_return(connection)
expect(host).to receive(:enable_query_cache!).once
expect { |b| lb.read(&b) }.to yield_with_args(connection) expect { |b| lb.read(&b) }.to yield_with_args(connection)
expect(RequestStore[described_class::ENSURE_CACHING_KEY]).to be true
end
context 'when :query_cache_for_load_balancing feature flag is disabled' do
before do
stub_feature_flags(query_cache_for_load_balancing: false)
end
it 'yields a connection for a read without enabling query cache' do
connection = double(:connection)
host = double(:host)
allow(lb).to receive(:host).and_return(host)
expect(host).to receive(:connection).and_return(connection)
expect(host).not_to receive(:enable_query_cache!)
expect { |b| lb.read(&b) }.to yield_with_args(connection)
end
end end
it 'marks hosts that are offline' do it 'marks hosts that are offline' do
...@@ -142,10 +161,14 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer do ...@@ -142,10 +161,14 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer do
describe '#release_host' do describe '#release_host' do
it 'releases the host and its connection' do it 'releases the host and its connection' do
lb.host host = lb.host
expect(host).to receive(:disable_query_cache!)
lb.release_host lb.release_host
expect(RequestStore[described_class::CACHE_KEY]).to be_nil expect(RequestStore[described_class::CACHE_KEY]).to be_nil
expect(RequestStore[described_class::ENSURE_CACHING_KEY]).to be_nil
end end
end end
......
...@@ -31,8 +31,10 @@ RSpec.describe 'layouts/application' do ...@@ -31,8 +31,10 @@ RSpec.describe 'layouts/application' do
it 'has the notification dot' do it 'has the notification dot' do
render render
expect(rendered).to have_css('span', class: 'header-user-notification-dot') expect(rendered).to have_css('li', class: 'header-user') do
expect(rendered).to have_selector(track_selector) expect(rendered).to have_css('span', class: 'notification-dot')
expect(rendered).to have_selector(track_selector)
end
end end
end end
...@@ -40,8 +42,10 @@ RSpec.describe 'layouts/application' do ...@@ -40,8 +42,10 @@ RSpec.describe 'layouts/application' do
it 'does not have the notification dot' do it 'does not have the notification dot' do
render render
expect(rendered).not_to have_css('span', class: 'header-user-notification-dot') expect(rendered).to have_css('li', class: 'header-user') do
expect(rendered).not_to have_selector(track_selector) expect(rendered).not_to have_css('span', class: 'notification-dot')
expect(rendered).not_to have_selector(track_selector)
end
end end
end end
end end
......
...@@ -25,6 +25,7 @@ Migration/UpdateLargeTable: ...@@ -25,6 +25,7 @@ Migration/UpdateLargeTable:
- :project_authorizations - :project_authorizations
- :projects - :projects
- :project_ci_cd_settings - :project_ci_cd_settings
- :project_settings
- :project_features - :project_features
- :push_event_payloads - :push_event_payloads
- :resource_label_events - :resource_label_events
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
set -e set -e
WORKHORSE_DIR=workhorse/ WORKHORSE_DIR=workhorse/
WORKHORSE_REF="v$(cat GITLAB_WORKHORSE_VERSION)" WORKHORSE_REF="v$(cat GITLAB_WORKHORSE_VERSION)"
WORKHORSE_URL=${GITLAB_WORKHORSE_URL:-https://gitlab.com/gitlab-org/gitlab-workhorse.git}
if [ $# -gt 1 ] || ([ $# = 1 ] && [ x$1 != xcheck ]); then if [ $# -gt 1 ] || ([ $# = 1 ] && [ x$1 != xcheck ]); then
echo "Usage: update-workhorse [check]" echo "Usage: update-workhorse [check]"
...@@ -15,7 +16,7 @@ if [ -n "$clean" ] ; then ...@@ -15,7 +16,7 @@ if [ -n "$clean" ] ; then
exit 1 exit 1
fi fi
git fetch https://gitlab.com/gitlab-org/gitlab-workhorse.git "$WORKHORSE_REF" git fetch "$WORKHORSE_URL" "$WORKHORSE_REF"
git rm -rf --quiet -- "$WORKHORSE_DIR" git rm -rf --quiet -- "$WORKHORSE_DIR"
git read-tree --prefix="$WORKHORSE_DIR" -u FETCH_HEAD git read-tree --prefix="$WORKHORSE_DIR" -u FETCH_HEAD
......
...@@ -167,6 +167,14 @@ RSpec.describe Repositories::GitHttpController do ...@@ -167,6 +167,14 @@ RSpec.describe Repositories::GitHttpController do
Projects::DailyStatisticsFinder.new(container).total_fetch_count Projects::DailyStatisticsFinder.new(container).total_fetch_count
}.from(0).to(1) }.from(0).to(1)
end end
it 'records a namespace onboarding progress action' do
expect_next_instance_of(OnboardingProgressService) do |service|
expect(service).to receive(:execute).with(action: :git_read)
end
send_request
end
end end
end end
end end
......
<div class='whats-new-notification-fixture-root'>
<div class='app' data-storage-key='storage-key'></div>
<div class='header-help'>
<div class='js-whats-new-notification-count'></div>
</div>
</div>
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { setNotification, getStorageKey } from '~/whats_new/utils/notification';
describe('~/whats_new/utils/notification', () => {
useLocalStorageSpy();
let wrapper;
const findNotificationEl = () => wrapper.querySelector('.header-help');
const findNotificationCountEl = () => wrapper.querySelector('.js-whats-new-notification-count');
const getAppEl = () => wrapper.querySelector('.app');
beforeEach(() => {
loadFixtures('static/whats_new_notification.html');
wrapper = document.querySelector('.whats-new-notification-fixture-root');
});
afterEach(() => {
wrapper.remove();
});
describe('setNotification', () => {
const subject = () => setNotification(getAppEl());
it("when storage key doesn't exist it adds notifications class", () => {
const notificationEl = findNotificationEl();
expect(notificationEl.classList).not.toContain('with-notifications');
subject();
expect(findNotificationCountEl()).toExist();
expect(notificationEl.classList).toContain('with-notifications');
});
it('removes class and count element when storage key is true', () => {
const notificationEl = findNotificationEl();
notificationEl.classList.add('with-notifications');
localStorage.setItem('storage-key', 'false');
expect(findNotificationCountEl()).toExist();
subject();
expect(findNotificationCountEl()).not.toExist();
expect(notificationEl.classList).not.toContain('with-notifications');
});
});
describe('getStorageKey', () => {
it('retrieves the storage key data attribute from the el', () => {
expect(getStorageKey(getAppEl())).toBe('storage-key');
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe OnboardingProgressService do
describe '#execute' do
let_it_be(:namespace) { build(:namespace) }
let(:action) { :namespace_action }
subject(:execute_service) { described_class.new(namespace).execute(action: action) }
it 'records a namespace onboarding progress action' do
expect(NamespaceOnboardingAction).to receive(:create_action)
.with(namespace, :namespace_action)
subject
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