From 893a66d84f9c6169c270cd5269771c3e3d6f8d81 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Tue, 11 Dec 2018 11:29:26 +0100 Subject: [PATCH] SONARCLOUD-270 Show latest notification in the Navbar --- server/sonar-web/src/main/js/api/news.ts | 70 +++++++- .../src/main/js/api/user-settings.ts | 41 +++++ .../embed-docs-modal/EmbedDocsPopupHelper.tsx | 6 +- .../app/components/nav/global/GlobalNav.css | 65 +++++++- .../app/components/nav/global/GlobalNav.tsx | 2 + .../nav/global/GlobalNavNotifications.tsx | 152 ++++++++++++++++++ .../components/nav/global/GlobalNavPlus.tsx | 2 +- .../__tests__/GlobalNavNotifications-test.tsx | 121 ++++++++++++++ .../__snapshots__/GlobalNav-test.tsx.snap | 1 + .../GlobalNavNotifications-test.tsx.snap | 85 ++++++++++ .../__snapshots__/GlobalNavPlus-test.tsx.snap | 2 +- server/sonar-web/src/main/js/app/types.d.ts | 9 ++ .../icons-components/NotificationIcon.tsx | 52 ++++++ .../src/main/js/store/rootReducer.ts | 4 + server/sonar-web/src/main/js/store/users.ts | 87 +++++++--- .../resources/org/sonar/l10n/core.properties | 1 + 16 files changed, 673 insertions(+), 27 deletions(-) create mode 100644 server/sonar-web/src/main/js/api/user-settings.ts create mode 100644 server/sonar-web/src/main/js/app/components/nav/global/GlobalNavNotifications.tsx create mode 100644 server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavNotifications-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavNotifications-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/icons-components/NotificationIcon.tsx diff --git a/server/sonar-web/src/main/js/api/news.ts b/server/sonar-web/src/main/js/api/news.ts index 6e246198027..4b11b157763 100644 --- a/server/sonar-web/src/main/js/api/news.ts +++ b/server/sonar-web/src/main/js/api/news.ts @@ -30,10 +30,48 @@ export interface PrismicNews { uid: string; } +interface PrismicResult { + data: { + notification: string; + publication_date: string; + body: PrismicResultFeature[]; + }; +} + +interface PrismicResultFeature { + items: Array<{ + category: { + data: { + color: string; + name: string; + }; + }; + }>; + primary: { + description: string; + read_more_link: { + url?: string; + }; + }; +} + +export interface PrismicFeatureNews { + notification: string; + publicationDate: string; + features: Array<{ + categories: Array<{ + color: string; + name: string; + }>; + description: string; + readMore?: string; + }>; +} + const PRISMIC_API_URL = 'https://sonarsource.cdn.prismic.io/api/v2'; export function fetchPrismicRefs() { - return getCorsJSON(PRISMIC_API_URL).then((response: { refs: Array }) => { + return getCorsJSON(PRISMIC_API_URL).then((response: { refs: PrismicRef[] }) => { const master = response && response.refs.find(ref => ref.id === 'master'); if (!master) { return Promise.reject('No master ref found'); @@ -58,5 +96,33 @@ export function fetchPrismicNews(data: { pageSize: data.ps || 1, q, ref: data.ref - }).then(({ results }: { results: Array }) => results); + }).then(({ results }: { results: PrismicNews[] }) => results); +} + +export function fetchPrismicFeatureNews(data: { + accessToken: string; + ps?: number; + ref: string; +}): Promise { + const q = ['[[at(document.type, "sc_product_news")]]']; + return getCorsJSON(PRISMIC_API_URL + '/documents/search', { + access_token: data.accessToken, + orderings: '[document.first_publication_date desc]', + pageSize: data.ps || 1, + q, + fetchLinks: 'sc_category.color,sc_category.name', + ref: data.ref + }).then(({ results }: { results: PrismicResult[] }) => { + return results.map(result => { + return { + notification: result.data.notification, + publicationDate: result.data.publication_date, + features: result.data.body.map(feature => ({ + categories: feature.items.map(item => item.category.data), + description: feature.primary.description, + readMore: feature.primary.read_more_link.url + })) + }; + }); + }); } diff --git a/server/sonar-web/src/main/js/api/user-settings.ts b/server/sonar-web/src/main/js/api/user-settings.ts new file mode 100644 index 00000000000..f28c9fbbe07 --- /dev/null +++ b/server/sonar-web/src/main/js/api/user-settings.ts @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { getJSON, post } from '../helpers/request'; +import throwGlobalError from '../app/utils/throwGlobalError'; + +export function setUserSetting(data: T.CurrentUserSettingData) { + return post('/api/user_settings/set', data) + .catch(() => Promise.resolve()) // TODO Remove mock. + .catch(throwGlobalError); +} + +export function listUserSettings(): Promise<{ userSettings: T.CurrentUserSettingData[] }> { + return getJSON('/api/user_settings/list') + .catch(() => { + // TODO Remove mock. + return { + userSettings: [ + { key: 'notificationsReadDate', value: '2018-12-01T12:07:19+0000' }, + { key: 'notificationsOptOut', value: 'false' } + ] + }; + }) + .catch(throwGlobalError); +} diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx index d02c95f18d7..9e040463027 100644 --- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx @@ -78,7 +78,11 @@ export default class EmbedDocsPopupHelper extends React.PureComponent<{}, State> onRequestClose={this.closeHelp} open={this.state.helpOpen} overlay={}> - + diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.css b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.css index bdad4103902..c4eb2f4b698 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.css +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.css @@ -50,8 +50,7 @@ border: none !important; } -.navbar-help, -.navbar-plus { +.navbar-icon { display: inline-block; height: var(--globalNavHeight); padding: calc(var(--globalNavHeight) - var(--globalNavContentHeight)) 12px !important; @@ -98,6 +97,68 @@ .global-navbar-menu-right { flex: 1; justify-content: flex-end; + margin-left: calc(5 * var(--gridSize)); +} + +.navbar-latest-notification { + flex: 0 1 350px; + text-align: right; + overflow: hidden; +} + +.navbar-latest-notification-wrapper { + position: relative; + display: inline-block; + padding: var(--gridSize) 34px var(--gridSize) 50px; + height: 28px; + max-width: 100%; + box-sizing: border-box; + overflow: hidden; + vertical-align: middle; + font-size: var(--smallFontSize); + color: var(--sonarcloudBlack500); + background-color: black; + text-overflow: ellipsis; + white-space: nowrap; + border-radius: 3px; + cursor: pointer; +} + +.navbar-latest-notification-wrapper:hover { + color: var(--sonarcloudBlack300); +} + +.navbar-latest-notification-wrapper .badge { + position: absolute; + height: 18px; + margin-right: var(--gridSize); + left: calc(var(--gridSize) / 2); + top: 5px; + font-size: var(--verySmallFontSize); + text-transform: uppercase; + background-color: var(--lightBlue); + color: var(--darkBlue); +} + +.navbar-latest-notification-wrapper .label { + display: block; + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.navbar-latest-notification .navbar-icon { + position: absolute; + right: 0; + top: 0; + height: 28px; + padding: 9px var(--gridSize) !important; + border-left: 2px solid #262626; +} + +.navbar-latest-notification .navbar-icon:hover path { + fill: var(--sonarcloudBlack300) !important; } .global-navbar-menu-right .navbar-search { diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx index ef1d79fe8ce..0c69b523c1a 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx @@ -22,6 +22,7 @@ import { connect } from 'react-redux'; import GlobalNavBranding, { SonarCloudNavBranding } from './GlobalNavBranding'; import GlobalNavMenu from './GlobalNavMenu'; import GlobalNavExplore from './GlobalNavExplore'; +import GlobalNavNotifications from './GlobalNavNotifications'; import GlobalNavUserContainer from './GlobalNavUserContainer'; import Search from '../../search/Search'; import EmbedDocsPopupHelper from '../../embed-docs-modal/EmbedDocsPopupHelper'; @@ -57,6 +58,7 @@ export class GlobalNav extends React.PureComponent {