diff options
Diffstat (limited to 'app/assets/javascripts/gitlab_version_check')
5 files changed, 358 insertions, 34 deletions
diff --git a/app/assets/javascripts/gitlab_version_check/components/security_patch_upgrade_alert.vue b/app/assets/javascripts/gitlab_version_check/components/security_patch_upgrade_alert.vue new file mode 100644 index 00000000000..89dc68ec73e --- /dev/null +++ b/app/assets/javascripts/gitlab_version_check/components/security_patch_upgrade_alert.vue @@ -0,0 +1,76 @@ +<script> +import { GlAlert, GlSprintf, GlLink, GlButton } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import Tracking from '~/tracking'; +import { UPGRADE_DOCS_URL, ABOUT_RELEASES_PAGE } from '../constants'; + +export default { + name: 'SecurityPatchUpgradeAlert', + i18n: { + alertTitle: s__('VersionCheck|Critical security upgrade available'), + alertBody: s__( + 'VersionCheck|You are currently on version %{currentVersion}. We strongly recommend upgrading your GitLab installation. %{link}', + ), + learnMore: s__('VersionCheck|Learn more about this critical security release.'), + primaryButtonText: s__('VersionCheck|Upgrade now'), + }, + components: { + GlAlert, + GlSprintf, + GlLink, + GlButton, + }, + mixins: [Tracking.mixin()], + props: { + currentVersion: { + type: String, + required: true, + }, + }, + mounted() { + this.track('render', { + label: 'security_patch_upgrade_alert', + property: this.currentVersion, + }); + }, + methods: { + trackLearnMoreClick() { + this.track('click_link', { + label: 'security_patch_upgrade_alert_learn_more', + property: this.currentVersion, + }); + }, + trackUpgradeNowClick() { + this.track('click_link', { + label: 'security_patch_upgrade_alert_upgrade_now', + property: this.currentVersion, + }); + }, + }, + UPGRADE_DOCS_URL, + ABOUT_RELEASES_PAGE, +}; +</script> + +<template> + <gl-alert :title="$options.i18n.alertTitle" variant="danger" :dismissible="false"> + <gl-sprintf :message="$options.i18n.alertBody"> + <template #currentVersion> + <span class="gl-font-weight-bold">{{ currentVersion }}</span> + </template> + <template #link> + <gl-link :href="$options.ABOUT_RELEASES_PAGE" @click="trackLearnMoreClick">{{ + $options.i18n.learnMore + }}</gl-link> + </template> + </gl-sprintf> + <template #actions> + <gl-button + :href="$options.UPGRADE_DOCS_URL" + variant="confirm" + @click="trackUpgradeNowClick" + >{{ $options.i18n.primaryButtonText }}</gl-button + > + </template> + </gl-alert> +</template> diff --git a/app/assets/javascripts/gitlab_version_check/components/security_patch_upgrade_alert_modal.vue b/app/assets/javascripts/gitlab_version_check/components/security_patch_upgrade_alert_modal.vue new file mode 100644 index 00000000000..4638ba8a268 --- /dev/null +++ b/app/assets/javascripts/gitlab_version_check/components/security_patch_upgrade_alert_modal.vue @@ -0,0 +1,160 @@ +<script> +import { GlModal, GlSprintf, GlLink, GlButton } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; +import { glEmojiTag } from '~/emoji'; +import { s__, sprintf } from '~/locale'; +import Tracking from '~/tracking'; +import { getHideAlertModalCookie, setHideAlertModalCookie } from '../utils'; +import { + UPGRADE_DOCS_URL, + ABOUT_RELEASES_PAGE, + ALERT_MODAL_ID, + TRACKING_ACTIONS, + TRACKING_LABELS, +} from '../constants'; + +export default { + name: 'SecurityPatchUpgradeAlertModal', + i18n: { + modalTitle: s__('VersionCheck|Important notice - Critical security release'), + modalBodyNoStableVersions: s__( + 'VersionCheck|You are currently on version %{currentVersion}! We strongly recommend upgrading your GitLab installation immediately.', + ), + modalBodyStableVersions: s__( + 'VersionCheck|You are currently on version %{currentVersion}! We strongly recommend upgrading your GitLab installation to one of the following versions immediately: %{latestStableVersions}.', + ), + modalDetails: s__('VersionCheck|%{details}'), + learnMore: s__('VersionCheck|Learn more about this critical security release.'), + primaryButtonText: s__('VersionCheck|Upgrade now'), + secondaryButtonText: s__('VersionCheck|Remind me again in 3 days'), + }, + components: { + GlModal, + GlSprintf, + GlLink, + GlButton, + }, + directives: { + SafeHtml, + }, + mixins: [Tracking.mixin()], + props: { + currentVersion: { + type: String, + required: true, + }, + details: { + type: String, + required: false, + default: '', + }, + latestStableVersions: { + type: Array, + required: false, + default: () => [], + }, + }, + data() { + return { + visible: true, + }; + }, + computed: { + alertEmoji() { + return glEmojiTag('rotating_light'); + }, + modalBody() { + if (this.latestStableVersions?.length > 0) { + return this.$options.i18n.modalBodyStableVersions; + } + + return this.$options.i18n.modalBodyNoStableVersions; + }, + modalDetails() { + return sprintf(this.$options.i18n.modalDetails, { details: this.details }); + }, + latestStableVersionsStrings() { + return this.latestStableVersions?.length > 0 ? this.latestStableVersions.join(', ') : ''; + }, + }, + created() { + if (getHideAlertModalCookie(this.currentVersion)) { + this.visible = false; + return; + } + + this.dispatchTrackingEvent(TRACKING_ACTIONS.RENDER, TRACKING_LABELS.MODAL); + }, + methods: { + dispatchTrackingEvent(action, label) { + this.track(action, { + label, + property: this.currentVersion, + }); + }, + trackLearnMoreClick() { + this.dispatchTrackingEvent(TRACKING_ACTIONS.CLICK_LINK, TRACKING_LABELS.LEARN_MORE_LINK); + }, + trackRemindMeLaterClick() { + this.dispatchTrackingEvent(TRACKING_ACTIONS.CLICK_BUTTON, TRACKING_LABELS.REMIND_ME_BTN); + setHideAlertModalCookie(this.currentVersion); + this.$refs.alertModal.hide(); + }, + trackUpgradeNowClick() { + this.dispatchTrackingEvent(TRACKING_ACTIONS.CLICK_LINK, TRACKING_LABELS.UPGRADE_BTN_LINK); + setHideAlertModalCookie(this.currentVersion); + }, + trackModalDismissed() { + this.dispatchTrackingEvent(TRACKING_ACTIONS.CLICK_BUTTON, TRACKING_LABELS.DISMISS); + }, + }, + safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] }, + UPGRADE_DOCS_URL, + ABOUT_RELEASES_PAGE, + ALERT_MODAL_ID, +}; +</script> + +<template> + <gl-modal + ref="alertModal" + :modal-id="$options.ALERT_MODAL_ID" + :visible="visible" + @close="trackModalDismissed" + > + <template #modal-title> + <span v-safe-html:[$options.safeHtmlConfig]="alertEmoji"></span> + <span data-testid="alert-modal-title">{{ $options.i18n.modalTitle }}</span> + </template> + <template #default> + <div data-testid="alert-modal-body" class="gl-mb-6"> + <gl-sprintf :message="modalBody"> + <template #currentVersion> + <span class="gl-font-weight-bold">{{ currentVersion }}</span> + </template> + <template #latestStableVersions> + <span class="gl-font-weight-bold">{{ latestStableVersionsStrings }}</span> + </template> + </gl-sprintf> + </div> + <div v-if="details" data-testid="alert-modal-details" class="gl-mb-6"> + {{ modalDetails }} + </div> + <gl-link :href="$options.ABOUT_RELEASES_PAGE" @click="trackLearnMoreClick">{{ + $options.i18n.learnMore + }}</gl-link> + </template> + <template #modal-footer> + <gl-button data-testid="alert-modal-remind-button" @click="trackRemindMeLaterClick">{{ + $options.i18n.secondaryButtonText + }}</gl-button> + <gl-button + data-testid="alert-modal-upgrade-button" + :href="$options.UPGRADE_DOCS_URL" + variant="confirm" + @click="trackUpgradeNowClick" + >{{ $options.i18n.primaryButtonText }}</gl-button + > + </template> + </gl-modal> +</template> diff --git a/app/assets/javascripts/gitlab_version_check/constants.js b/app/assets/javascripts/gitlab_version_check/constants.js index 259723a4e22..049397148ab 100644 --- a/app/assets/javascripts/gitlab_version_check/constants.js +++ b/app/assets/javascripts/gitlab_version_check/constants.js @@ -7,3 +7,25 @@ export const STATUS_TYPES = { }; export const UPGRADE_DOCS_URL = helpPagePath('update/index'); + +export const ABOUT_RELEASES_PAGE = 'https://about.gitlab.com/releases/categories/releases/'; + +export const ALERT_MODAL_ID = 'security-patch-upgrade-alert-modal'; + +export const COOKIE_EXPIRATION = 3; + +export const COOKIE_SUFFIX = '-hide-alert-modal'; + +export const TRACKING_ACTIONS = { + RENDER: 'render', + CLICK_LINK: 'click_link', + CLICK_BUTTON: 'click_button', +}; + +export const TRACKING_LABELS = { + MODAL: 'security_patch_upgrade_alert_modal', + LEARN_MORE_LINK: 'security_patch_upgrade_alert_modal_learn_more', + REMIND_ME_BTN: 'security_patch_upgrade_alert_modal_remind_3_days', + UPGRADE_BTN_LINK: 'security_patch_upgrade_alert_modal_upgrade_now', + DISMISS: 'security_patch_upgrade_alert_modal_close', +}; diff --git a/app/assets/javascripts/gitlab_version_check/index.js b/app/assets/javascripts/gitlab_version_check/index.js index 203ce10ef57..edb7e9abe49 100644 --- a/app/assets/javascripts/gitlab_version_check/index.js +++ b/app/assets/javascripts/gitlab_version_check/index.js @@ -1,50 +1,98 @@ import Vue from 'vue'; -import * as Sentry from '@sentry/browser'; -import { parseBoolean } from '~/lib/utils/common_utils'; -import axios from '~/lib/utils/axios_utils'; -import { joinPaths } from '~/lib/utils/url_utility'; +import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import GitlabVersionCheckBadge from './components/gitlab_version_check_badge.vue'; +import SecurityPatchUpgradeAlert from './components/security_patch_upgrade_alert.vue'; +import SecurityPatchUpgradeAlertModal from './components/security_patch_upgrade_alert_modal.vue'; -const mountGitlabVersionCheckBadge = ({ el, status }) => { - const { size } = el.dataset; +const mountGitlabVersionCheckBadge = (el) => { + const { size, version } = el.dataset; const actionable = parseBoolean(el.dataset.actionable); - return new Vue({ - el, - render(createElement) { - return createElement(GitlabVersionCheckBadge, { - props: { - size, - actionable, - status, - }, - }); - }, - }); + try { + const { severity } = JSON.parse(version); + + // If no severity (status) data don't worry about rendering + if (!severity) { + return null; + } + + return new Vue({ + el, + render(createElement) { + return createElement(GitlabVersionCheckBadge, { + props: { + size, + actionable, + status: severity, + }, + }); + }, + }); + } catch { + return null; + } }; -export default async () => { - const versionCheckBadges = [...document.querySelectorAll('.js-gitlab-version-check-badge')]; +const mountSecurityPatchUpgradeAlert = (el) => { + const { currentVersion } = el.dataset; - // If there are no version check elements, exit out - if (versionCheckBadges?.length <= 0) { + try { + return new Vue({ + el, + render(createElement) { + return createElement(SecurityPatchUpgradeAlert, { + props: { + currentVersion, + }, + }); + }, + }); + } catch { return null; } +}; - const status = await axios - .get(joinPaths('/', gon.relative_url_root, '/admin/version_check.json')) - .then((res) => { - return res.data?.severity; - }) - .catch((e) => { - Sentry.captureException(e); - return null; +const mountSecurityPatchUpgradeAlertModal = (el) => { + const { currentVersion, version } = el.dataset; + + try { + const { details, latestStableVersions } = convertObjectPropsToCamelCase(JSON.parse(version)); + + return new Vue({ + el, + render(createElement) { + return createElement(SecurityPatchUpgradeAlertModal, { + props: { + currentVersion, + details, + latestStableVersions, + }, + }); + }, }); + } catch { + return null; + } +}; + +export default () => { + const renderedApps = []; - // If we don't have a status there is nothing to render - if (status) { - return versionCheckBadges.map((el) => mountGitlabVersionCheckBadge({ el, status })); + const securityPatchUpgradeAlert = document.getElementById('js-security-patch-upgrade-alert'); + const securityPatchUpgradeAlertModal = document.getElementById( + 'js-security-patch-upgrade-alert-modal', + ); + const versionCheckBadges = [...document.querySelectorAll('.js-gitlab-version-check-badge')]; + + if (securityPatchUpgradeAlert) { + renderedApps.push(mountSecurityPatchUpgradeAlert(securityPatchUpgradeAlert)); } - return null; + if (securityPatchUpgradeAlertModal) { + renderedApps.push(mountSecurityPatchUpgradeAlertModal(securityPatchUpgradeAlertModal)); + } + + renderedApps.push(...versionCheckBadges.map((el) => mountGitlabVersionCheckBadge(el))); + + return renderedApps; }; diff --git a/app/assets/javascripts/gitlab_version_check/utils.js b/app/assets/javascripts/gitlab_version_check/utils.js new file mode 100644 index 00000000000..d2f4349483c --- /dev/null +++ b/app/assets/javascripts/gitlab_version_check/utils.js @@ -0,0 +1,18 @@ +import { setCookie, getCookie, parseBoolean } from '~/lib/utils/common_utils'; +import { COOKIE_EXPIRATION, COOKIE_SUFFIX } from './constants'; + +const buildKey = (currentVersion) => { + return `${currentVersion}${COOKIE_SUFFIX}`; +}; + +export const setHideAlertModalCookie = (currentVersion) => { + const key = buildKey(currentVersion); + + setCookie(key, true, { expires: COOKIE_EXPIRATION }); +}; + +export const getHideAlertModalCookie = (currentVersion) => { + const key = buildKey(currentVersion); + + return parseBoolean(getCookie(key)); +}; |