Commit 4553557a authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch 'jswain_whats_new_notification' into 'master'

Whats new notification dot and count

See merge request gitlab-org/gitlab!42494
parents 61c0915d 647ec63b
...@@ -16,6 +16,15 @@ function initDeferred() { ...@@ -16,6 +16,15 @@ function initDeferred() {
const whatsNewTriggerEl = document.querySelector('.js-whats-new-trigger'); const whatsNewTriggerEl = document.querySelector('.js-whats-new-trigger');
if (whatsNewTriggerEl) { if (whatsNewTriggerEl) {
const storageKey = whatsNewTriggerEl.getAttribute('data-storage-key');
$('.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', () => { whatsNewTriggerEl.addEventListener('click', () => {
import(/* webpackChunkName: 'whatsNewApp' */ '~/whats_new') import(/* webpackChunkName: 'whatsNewApp' */ '~/whats_new')
.then(({ default: initWhatsNew }) => { .then(({ default: initWhatsNew }) => {
......
...@@ -15,6 +15,11 @@ export default { ...@@ -15,6 +15,11 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
storageKey: {
type: String,
required: true,
default: null,
},
}, },
computed: { computed: {
...mapState(['open']), ...mapState(['open']),
...@@ -31,7 +36,7 @@ export default { ...@@ -31,7 +36,7 @@ export default {
}, },
}, },
mounted() { mounted() {
this.openDrawer(); this.openDrawer(this.storageKey);
}, },
methods: { methods: {
...mapActions(['openDrawer', 'closeDrawer']), ...mapActions(['openDrawer', 'closeDrawer']),
...@@ -41,7 +46,7 @@ export default { ...@@ -41,7 +46,7 @@ export default {
<template> <template>
<div> <div>
<gl-drawer class="mt-6" :open="open" @close="closeDrawer"> <gl-drawer class="whats-new-drawer" :open="open" @close="closeDrawer">
<template #header> <template #header>
<h4 class="page-title my-2">{{ __("What's new at GitLab") }}</h4> <h4 class="page-title my-2">{{ __("What's new at GitLab") }}</h4>
</template> </template>
...@@ -69,5 +74,6 @@ export default { ...@@ -69,5 +74,6 @@ export default {
</div> </div>
</div> </div>
</gl-drawer> </gl-drawer>
<div v-if="open" class="whats-new-modal-backdrop modal-backdrop"></div>
</div> </div>
</template> </template>
...@@ -20,6 +20,7 @@ export default () => { ...@@ -20,6 +20,7 @@ export default () => {
return createElement('app', { return createElement('app', {
props: { props: {
features: whatsNewElm.getAttribute('data-features'), features: whatsNewElm.getAttribute('data-features'),
storageKey: whatsNewElm.getAttribute('data-storage-key'),
}, },
}); });
}, },
......
...@@ -4,7 +4,11 @@ export default { ...@@ -4,7 +4,11 @@ export default {
closeDrawer({ commit }) { closeDrawer({ commit }) {
commit(types.CLOSE_DRAWER); commit(types.CLOSE_DRAWER);
}, },
openDrawer({ commit }) { openDrawer({ commit }, storageKey) {
commit(types.OPEN_DRAWER); commit(types.OPEN_DRAWER);
if (storageKey) {
localStorage.setItem(storageKey, JSON.stringify(false));
}
}, },
}; };
.whats-new-drawer {
margin-top: $header-height;
@include gl-shadow-none;
}
.with-performance-bar .whats-new-drawer {
margin-top: calc(#{$performance-bar-height} + #{$header-height});
}
.gl-badge.whats-new-item-badge { .gl-badge.whats-new-item-badge {
background-color: $purple-light; background-color: $purple-light;
color: $purple; color: $purple;
font-weight: bold; @include gl-font-weight-bold;
} }
.whats-new-item-image { .whats-new-item-image {
border-color: $gray-50; border-color: $gray-50;
} }
.whats-new-modal-backdrop {
z-index: 9;
}
.whats-new-notification-count {
@include gl-bg-gray-900;
@include gl-font-sm;
@include gl-line-height-normal;
@include gl-text-white;
@include gl-vertical-align-top;
border-radius: 20px;
padding: 3px 10px;
}
...@@ -3,6 +3,24 @@ ...@@ -3,6 +3,24 @@
module WhatsNewHelper module WhatsNewHelper
EMPTY_JSON = ''.to_json EMPTY_JSON = ''.to_json
def whats_new_most_recent_release_items_count
items = parsed_most_recent_release_items
return unless items.is_a?(Array)
items.count
end
def whats_new_storage_key
items = parsed_most_recent_release_items
return unless items.is_a?(Array)
release = items.first.try(:[], 'release')
['display-whats-new-notification', release].compact.join('-')
end
def whats_new_most_recent_release_items def whats_new_most_recent_release_items
YAML.load_file(most_recent_release_file_path).to_json YAML.load_file(most_recent_release_file_path).to_json
...@@ -14,6 +32,10 @@ module WhatsNewHelper ...@@ -14,6 +32,10 @@ module WhatsNewHelper
private private
def parsed_most_recent_release_items
Gitlab::Json.parse(whats_new_most_recent_release_items)
end
def most_recent_release_file_path def most_recent_release_file_path
Dir.glob(files_path).max Dir.glob(files_path).max
end end
......
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
= sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left') = sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
- if ::Feature.enabled?(:whats_new_drawer) - if ::Feature.enabled?(:whats_new_drawer)
#whats-new-app{ data: { features: whats_new_most_recent_release_items } } #whats-new-app{ data: { features: whats_new_most_recent_release_items, storage_key: whats_new_storage_key } }
- if can?(current_user, :update_user_status, current_user) - if can?(current_user, :update_user_status, current_user)
.js-set-status-modal-wrapper{ data: { current_emoji: current_user.status.present? ? current_user.status.emoji : '', current_message: current_user.status.present? ? current_user.status.message : '' } } .js-set-status-modal-wrapper{ data: { current_emoji: current_user.status.present? ? current_user.status.emoji : '', current_message: current_user.status.present? ? current_user.status.message : '' } }
- if ::Feature.enabled?(:whats_new_dropdown) - if ::Feature.enabled?(:whats_new_dropdown)
- if ::Feature.enabled?(:whats_new_drawer) - if ::Feature.enabled?(:whats_new_drawer)
%li %li
%button.js-whats-new-trigger{ type: 'button' } %button.js-whats-new-trigger{ type: 'button', data: { storage_key: whats_new_storage_key } }
= _("See what's new at GitLab") = _("See what's new at GitLab")
%span.js-whats-new-notification-count.whats-new-notification-count.gl-ml-4
= whats_new_most_recent_release_items_count
- else - else
%li %li
= link_to _("See what's new at GitLab"), "#{promo_url}/releases/gitlab-com/", target: '_blank', rel: 'noopener noreferrer', data: { track_event: 'click_whats_new', track_property: 'question_menu' } = link_to _("See what's new at GitLab"), "#{promo_url}/releases/gitlab-com/", target: '_blank', rel: 'noopener noreferrer', data: { track_event: 'click_whats_new', track_property: 'question_menu' }
# frozen_string_literal: true
require "spec_helper"
RSpec.describe "renders a `whats new` dropdown item", :js do
let_it_be(:user) { create(:user) }
before do
stub_feature_flags(whats_new_dropdown: true, whats_new_drawer: true)
sign_in(user)
end
it 'shows notification 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_button(text: "See what's new at GitLab")
expect(page).to have_selector('.js-whats-new-notification-count')
find('button', text: "See what's new at GitLab").click
end
find('.whats-new-drawer .gl-drawer-close-button').click
find('.header-help-dropdown-toggle').click
page.within '.header-help' do
expect(page).to have_button(text: "See what's new at GitLab")
expect(page).not_to have_selector('.js-whats-new-notification-count')
end
end
end
...@@ -11,7 +11,7 @@ describe('App', () => { ...@@ -11,7 +11,7 @@ describe('App', () => {
let store; let store;
let actions; let actions;
let state; let state;
let propsData = { features: '[ {"title":"Whats New Drawer"} ]' }; let propsData = { features: '[ {"title":"Whats New Drawer"} ]', storageKey: 'storage-key' };
const buildWrapper = () => { const buildWrapper = () => {
actions = { actions = {
...@@ -51,6 +51,7 @@ describe('App', () => { ...@@ -51,6 +51,7 @@ describe('App', () => {
it('dispatches openDrawer when mounted', () => { it('dispatches openDrawer when mounted', () => {
expect(actions.openDrawer).toHaveBeenCalled(); expect(actions.openDrawer).toHaveBeenCalled();
expect(actions.openDrawer).toHaveBeenCalledWith(expect.any(Object), 'storage-key');
}); });
it('dispatches closeDrawer when clicking close', () => { it('dispatches closeDrawer when clicking close', () => {
...@@ -71,7 +72,7 @@ describe('App', () => { ...@@ -71,7 +72,7 @@ describe('App', () => {
}); });
it('handles bad json argument gracefully', () => { it('handles bad json argument gracefully', () => {
propsData = { features: 'this is not json' }; propsData = { features: 'this is not json', storageKey: 'storage-key' };
buildWrapper(); buildWrapper();
expect(getDrawer().exists()).toBe(true); expect(getDrawer().exists()).toBe(true);
......
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import actions from '~/whats_new/store/actions'; import actions from '~/whats_new/store/actions';
import * as types from '~/whats_new/store/mutation_types'; import * as types from '~/whats_new/store/mutation_types';
describe('whats new actions', () => { describe('whats new actions', () => {
describe('openDrawer', () => { describe('openDrawer', () => {
useLocalStorageSpy();
it('should commit openDrawer', () => { it('should commit openDrawer', () => {
testAction(actions.openDrawer, {}, {}, [{ type: types.OPEN_DRAWER }]); testAction(actions.openDrawer, 'storage-key', {}, [{ type: types.OPEN_DRAWER }]);
expect(window.localStorage.setItem).toHaveBeenCalledWith('storage-key', 'false');
}); });
}); });
......
...@@ -3,6 +3,52 @@ ...@@ -3,6 +3,52 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe WhatsNewHelper do RSpec.describe WhatsNewHelper do
describe '#whats_new_storage_key' do
subject { helper.whats_new_storage_key }
before do
allow(helper).to receive(:whats_new_most_recent_release_items).and_return(json)
end
context 'when recent release items exist' do
let(:json) { [{ release: 84.0 }].to_json }
it { is_expected.to eq('display-whats-new-notification-84.0') }
context 'when the release items are missing the release key' do
let(:json) { [{ title: 'bells!' }].to_json }
it { is_expected.to eq('display-whats-new-notification') }
end
end
context 'when recent release items do NOT exist' do
let(:json) { WhatsNewHelper::EMPTY_JSON }
it { is_expected.to be_nil }
end
end
describe '#whats_new_most_recent_release_items_count' do
subject { helper.whats_new_most_recent_release_items_count }
before do
allow(helper).to receive(:whats_new_most_recent_release_items).and_return(json)
end
context 'when recent release items exist' do
let(:json) { [:bells, :and, :whistles].to_json }
it { is_expected.to eq(3) }
end
context 'when recent release items do NOT exist' do
let(:json) { WhatsNewHelper::EMPTY_JSON }
it { is_expected.to be_nil }
end
end
describe '#whats_new_most_recent_release_items' do describe '#whats_new_most_recent_release_items' do
let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) } let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
......
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