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 {
v-if="!popoverDismissed"
show
:target="target"
placement="rightbottom"
placement="right"
trigger="manual"
container="viewport"
:css-classes="['suggest-gitlab-ci-yml', 'ml-4']"
......
import $ from 'jquery';
import ContextualSidebar from './contextual_sidebar';
import initFlyOutNav from './fly_out_nav';
import { setNotification } from './whats_new/utils/notification';
function hideEndFade($scrollingTabs) {
$scrollingTabs.each(function scrollTabsLoop() {
......@@ -14,25 +15,17 @@ function hideEndFade($scrollingTabs) {
function initDeferred() {
$(document).trigger('init.scrolling-tabs');
const whatsNewTriggerEl = document.querySelector('.js-whats-new-trigger');
if (whatsNewTriggerEl) {
const storageKey = whatsNewTriggerEl.getAttribute('data-storage-key');
const appEl = document.getElementById('whats-new-app');
if (!appEl) return;
$('.header-help').on('show.bs.dropdown', () => {
const displayNotification = JSON.parse(localStorage.getItem(storageKey));
if (displayNotification === false) {
$('.js-whats-new-notification-count').remove();
}
});
whatsNewTriggerEl.addEventListener('click', () => {
import(/* webpackChunkName: 'whatsNewApp' */ '~/whats_new')
.then(({ default: initWhatsNew }) => {
initWhatsNew();
})
.catch(() => {});
});
}
setNotification(appEl);
document.querySelector('.js-whats-new-trigger').addEventListener('click', () => {
import(/* webpackChunkName: 'whatsNewApp' */ '~/whats_new')
.then(({ default: initWhatsNew }) => {
initWhatsNew(appEl);
})
.catch(() => {});
});
}
export default function initLayoutNav() {
......
import Vue from 'vue';
import { mapState } from 'vuex';
import App from './components/app.vue';
import store from './store';
import { getStorageKey, setNotification } from './utils/notification';
let whatsNewApp;
export default () => {
export default el => {
if (whatsNewApp) {
store.dispatch('openDrawer');
} else {
const whatsNewElm = document.getElementById('whats-new-app');
const storageKey = getStorageKey(el);
whatsNewApp = new Vue({
el: whatsNewElm,
el,
store,
components: {
App,
},
computed: {
...mapState(['open']),
},
watch: {
open() {
setNotification(el);
},
},
render(createElement) {
return createElement('app', {
props: {
storageKey: whatsNewElm.getAttribute('data-storage-key'),
},
props: { storageKey },
});
},
});
......
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 @@
@include transition(color);
}
a {
a,
.notification-dot {
@include transition(background-color, color, border);
}
......
......@@ -556,12 +556,17 @@
border: 1px solid $gray-normal;
}
.header-user-notification-dot {
.notification-dot {
background-color: $orange-300;
height: 12px;
width: 12px;
right: 8px;
top: -8px;
margin-top: -15px;
pointer-events: none;
visibility: hidden;
}
.with-notifications .notification-dot {
visibility: visible;
}
.with-performance-bar .navbar-gitlab {
......
......@@ -64,14 +64,20 @@
color: $search-and-nav-links;
> 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-avatar {
border-color: $search-and-nav-links;
}
.header-user-notification-dot {
border: 2px solid $nav-svg-color;
}
}
&:hover,
......@@ -84,9 +90,14 @@
fill: currentColor;
}
&.header-user-dropdown-toggle .header-user-notification-dot {
.notification-dot {
will-change: border-color, background-color;
border-color: $nav-svg-color + 33;
}
&.header-help-dropdown-toggle .notification-dot {
background-color: $white;
}
}
}
......@@ -101,9 +112,15 @@
}
}
&.header-user-dropdown-toggle .header-user-notification-dot {
.notification-dot {
border-color: $white;
}
&.header-help-dropdown-toggle {
.notification-dot {
background-color: $nav-svg-color;
}
}
}
.impersonated-user,
......
......@@ -80,6 +80,8 @@ module Repositories
return if Gitlab::Database.read_only?
return unless repo_type.project?
OnboardingProgressService.new(project.namespace).execute(action: :git_read)
if Feature.enabled?(:project_statistics_sync, project, default_enabled: true)
Projects::FetchStatisticsIncrementService.new(project).execute
else
......
......@@ -7,7 +7,8 @@ class NamespaceOnboardingAction < ApplicationRecord
ACTIONS = {
subscription_created: 1,
git_write: 2
git_write: 2,
git_read: 4
}.freeze
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 @@
%span.gl-sr-only
= s_('Nav|Help')
= sprite_icon('question')
%span.notification-dot.rounded-circle.gl-absolute
= sprite_icon('chevron-down', css_class: 'caret-down')
.dropdown-menu.dropdown-menu-right
= 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 (
merge_request_id integer,
comment_author_id integer,
comment text,
comment_timestamp timestamp with time zone
comment_timestamp timestamp with time zone,
finding_uuid uuid
);
CREATE SEQUENCE vulnerability_feedback_id_seq
......
......@@ -6,8 +6,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Requirements for Auto DevOps
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).
You can set up Auto DevOps for [Kubernetes](#auto-devops-requirements-for-kubernetes),
[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)
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
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
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 {
</div>
</div>
</a>
<gl-popover
:target="generateKey(epic)"
:title="epic.title"
triggers="hover"
placement="lefttop"
>
<gl-popover :target="generateKey(epic)" :title="epic.title" triggers="hover" placement="left">
<p class="text-secondary m-0">{{ timeframeString(epic) }}</p>
<p class="m-0">{{ popoverWeightText }}</p>
</gl-popover>
......
......@@ -162,7 +162,7 @@ export default {
<gl-popover
:target="`milestone-item-${milestone.id}`"
boundary="viewport"
placement="lefttop"
placement="left"
triggers="hover"
:title="milestone.title"
>
......
- 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
class Host
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 =
if defined?(PG)
......
......@@ -12,6 +12,7 @@ module Gitlab
# always returns a connection to the primary.
class LoadBalancer
CACHE_KEY = :gitlab_load_balancer_host
ENSURE_CACHING_KEY = 'ensure_caching'
attr_reader :host_list
......@@ -28,6 +29,8 @@ module Gitlab
conflict_retried = 0
while host
ensure_caching!
begin
return yield host.connection
rescue => error
......@@ -95,7 +98,12 @@ module Gitlab
# Releases the host and connection for the current thread.
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)
end
......@@ -169,6 +177,23 @@ module Gitlab
error.is_a?(PG::TRSerializationFailure)
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
......
......@@ -11,12 +11,14 @@ RSpec.describe "renders a `whats new` dropdown item", :js do
sign_in(user)
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
find('.header-help-dropdown-toggle').click
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_selector('.js-whats-new-notification-count')
......@@ -27,6 +29,7 @@ RSpec.describe "renders a `whats new` dropdown item", :js do
find('.header-help-dropdown-toggle').click
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).not_to have_selector('.js-whats-new-notification-count')
end
......
......@@ -2,16 +2,15 @@
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)) }
before do
allow(Gitlab::Database).to receive(:create_connection_pool)
.and_return(ActiveRecord::Base.connection_pool)
end
after do
RequestStore.delete(described_class::CACHE_KEY)
.and_return(pool)
end
def raise_and_wrap(wrapper, original)
......@@ -52,8 +51,28 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer do
allow(lb).to receive(:host).and_return(host)
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(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
it 'marks hosts that are offline' do
......@@ -142,10 +161,14 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer do
describe '#release_host' do
it 'releases the host and its connection' do
lb.host
host = lb.host
expect(host).to receive(:disable_query_cache!)
lb.release_host
expect(RequestStore[described_class::CACHE_KEY]).to be_nil
expect(RequestStore[described_class::ENSURE_CACHING_KEY]).to be_nil
end
end
......
......@@ -31,8 +31,10 @@ RSpec.describe 'layouts/application' do
it 'has the notification dot' do
render
expect(rendered).to have_css('span', class: 'header-user-notification-dot')
expect(rendered).to have_selector(track_selector)
expect(rendered).to have_css('li', class: 'header-user') do
expect(rendered).to have_css('span', class: 'notification-dot')
expect(rendered).to have_selector(track_selector)
end
end
end
......@@ -40,8 +42,10 @@ RSpec.describe 'layouts/application' do
it 'does not have the notification dot' do
render
expect(rendered).not_to have_css('span', class: 'header-user-notification-dot')
expect(rendered).not_to have_selector(track_selector)
expect(rendered).to have_css('li', class: 'header-user') do
expect(rendered).not_to have_css('span', class: 'notification-dot')
expect(rendered).not_to have_selector(track_selector)
end
end
end
end
......
......@@ -25,6 +25,7 @@ Migration/UpdateLargeTable:
- :project_authorizations
- :projects
- :project_ci_cd_settings
- :project_settings
- :project_features
- :push_event_payloads
- :resource_label_events
......
......@@ -2,6 +2,7 @@
set -e
WORKHORSE_DIR=workhorse/
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
echo "Usage: update-workhorse [check]"
......@@ -15,7 +16,7 @@ if [ -n "$clean" ] ; then
exit 1
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 read-tree --prefix="$WORKHORSE_DIR" -u FETCH_HEAD
......
......@@ -167,6 +167,14 @@ RSpec.describe Repositories::GitHttpController do
Projects::DailyStatisticsFinder.new(container).total_fetch_count
}.from(0).to(1)
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
......
<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