Commit 679823cc authored by David O'Regan's avatar David O'Regan

Merge branch '295266_1_5-update-version-check' into 'master'

GitLab Version - Update Version Status Indicator

See merge request gitlab-org/gitlab!77521
parents ac764452 e8826a3c
import Vue from 'vue';
import GitlabVersionCheck from '~/vue_shared/components/gitlab_version_check.vue';
const mountGitlabVersionCheck = (el) => {
const { size } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(GitlabVersionCheck, {
props: {
size,
},
});
},
});
};
export default () =>
[...document.querySelectorAll('.js-gitlab-version-check')].map(mountGitlabVersionCheck);
import $ from 'jquery';
import docs from '~/docs/docs_bundle'; import docs from '~/docs/docs_bundle';
import VersionCheckImage from '~/version_check_image'; import initGitlabVersionCheck from '~/gitlab_version_check';
docs(); docs();
VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); initGitlabVersionCheck();
export default class VersionCheckImage {
static bindErrorEvent(imageElement) {
// eslint-disable-next-line @gitlab/no-global-event-off
imageElement.off('error').on('error', () => imageElement.hide());
}
}
<script>
import { GlBadge } from '@gitlab/ui';
import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
const STATUS_TYPES = {
SUCCESS: 'success',
WARNING: 'warning',
DANGER: 'danger',
};
export default {
name: 'GitlabVersionCheck',
components: {
GlBadge,
},
props: {
size: {
type: String,
required: false,
default: 'md',
},
},
data() {
return {
status: null,
};
},
computed: {
title() {
if (this.status === STATUS_TYPES.SUCCESS) {
return s__('VersionCheck|Up to date');
} else if (this.status === STATUS_TYPES.WARNING) {
return s__('VersionCheck|Update available');
} else if (this.status === STATUS_TYPES.DANGER) {
return s__('VersionCheck|Update ASAP');
}
return null;
},
},
created() {
this.checkGitlabVersion();
},
methods: {
checkGitlabVersion() {
axios
.get('/admin/version_check.json')
.then((res) => {
if (res.data) {
this.status = res.data.severity;
}
})
.catch(() => {
// Silently fail
this.status = null;
});
},
},
};
</script>
<template>
<gl-badge v-if="status" class="version-check-badge" :variant="status" :size="size">{{
title
}}</gl-badge>
</template>
# frozen_string_literal: true # frozen_string_literal: true
module VersionCheckHelper module VersionCheckHelper
def version_status_badge def show_version_check?
return unless Rails.env.production? return false unless Gitlab::CurrentSettings.version_check_enabled
return unless Gitlab::CurrentSettings.version_check_enabled return false if User.single_user&.requires_usage_stats_consent?
return if User.single_user&.requires_usage_stats_consent?
image_tag VersionCheck.image_url, class: 'js-version-status-badge' current_user&.can_read_all_resources?
end end
def link_to_version def link_to_version
......
...@@ -118,9 +118,9 @@ ...@@ -118,9 +118,9 @@
.gl-card-body .gl-card-body
%h4 %h4
= s_('AdminArea|Components') = s_('AdminArea|Components')
- if Gitlab::CurrentSettings.version_check_enabled - if show_version_check?
.float-right .float-right
= version_status_badge .js-gitlab-version-check{ data: { "size": "lg" } }
= link_to(sprite_icon('question'), "https://gitlab.com/gitlab-org/gitlab/-/blob/master/CHANGELOG.md", class: 'gl-ml-2', target: '_blank', rel: 'noopener noreferrer') = link_to(sprite_icon('question'), "https://gitlab.com/gitlab-org/gitlab/-/blob/master/CHANGELOG.md", class: 'gl-ml-2', target: '_blank', rel: 'noopener noreferrer')
%p %p
= link_to _('GitLab'), general_admin_application_settings_path = link_to _('GitLab'), general_admin_application_settings_path
......
...@@ -4,12 +4,15 @@ ...@@ -4,12 +4,15 @@
= markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text) = markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
%hr %hr
%h1 .gl-display-flex.gl-align-items-flex-end
= default_brand_title %h1.gl-mt-5.gl-mb-3
- if user_signed_in? = default_brand_title
%span= link_to_version - if user_signed_in?
= version_status_badge %span= link_to_version
%hr - if show_version_check?
%span.gl-mt-5.gl-mb-3.gl-ml-3
.js-gitlab-version-check{ data: { "size": "lg" } }
%hr
- unless Gitlab::CurrentSettings.help_page_hide_commercial_content? - unless Gitlab::CurrentSettings.help_page_hide_commercial_content?
%p.slead %p.slead
......
import { shouldQrtlyReconciliationMount } from 'ee/billings/qrtly_reconciliation'; import { shouldQrtlyReconciliationMount } from 'ee/billings/qrtly_reconciliation';
import initGitlabVersionCheck from '~/gitlab_version_check';
shouldQrtlyReconciliationMount(); shouldQrtlyReconciliationMount();
initGitlabVersionCheck();
...@@ -16,14 +16,6 @@ class VersionCheck ...@@ -16,14 +16,6 @@ class VersionCheck
{ "REFERER": Gitlab.config.gitlab.url } { "REFERER": Gitlab.config.gitlab.url }
end end
# This is temporary and will be removed when the new UI is hooked up
# to the version_check.json endpoint.
def self.image_url
encoded_data = Base64.urlsafe_encode64(data.to_json)
"#{host}/check.svg?gitlab_info=#{encoded_data}"
end
def self.url def self.url
encoded_data = Base64.urlsafe_encode64(data.to_json) encoded_data = Base64.urlsafe_encode64(data.to_json)
......
...@@ -39116,6 +39116,15 @@ msgstr "" ...@@ -39116,6 +39116,15 @@ msgstr ""
msgid "Version %{versionNumber} (latest)" msgid "Version %{versionNumber} (latest)"
msgstr "" msgstr ""
msgid "VersionCheck|Up to date"
msgstr ""
msgid "VersionCheck|Update ASAP"
msgstr ""
msgid "VersionCheck|Update available"
msgstr ""
msgid "View Documentation" msgid "View Documentation"
msgstr "" msgstr ""
......
...@@ -28,21 +28,20 @@ RSpec.describe 'Help Pages' do ...@@ -28,21 +28,20 @@ RSpec.describe 'Help Pages' do
end end
end end
context 'in a production environment with version check enabled' do describe 'with version check enabled' do
let_it_be(:user) { create(:user) }
before do before do
stub_application_setting(version_check_enabled: true) stub_application_setting(version_check_enabled: true)
allow(User).to receive(:single_user).and_return(double(user, requires_usage_stats_consent?: false))
allow(user).to receive(:can_read_all_resources?).and_return(true)
stub_rails_env('production') sign_in(user)
allow(VersionCheck).to receive(:image_url).and_return('/version-check-url')
sign_in(create(:user))
visit help_path visit help_path
end end
it 'has a version check image' do it 'renders the version check badge' do
# Check `data-src` due to lazy image loading expect(page).to have_selector('.js-gitlab-version-check')
expect(find('.js-version-status-badge', visible: false)['data-src'])
.to end_with('/version-check-url')
end end
end end
......
import $ from 'jquery';
import ClassSpecHelper from 'helpers/class_spec_helper';
import VersionCheckImage from '~/version_check_image';
describe('VersionCheckImage', () => {
let testContext;
beforeEach(() => {
testContext = {};
});
describe('bindErrorEvent', () => {
ClassSpecHelper.itShouldBeAStaticMethod(VersionCheckImage, 'bindErrorEvent');
beforeEach(() => {
testContext.imageElement = $('<div></div>');
});
it('registers an error event', () => {
jest.spyOn($.prototype, 'on').mockImplementation(() => {});
// eslint-disable-next-line func-names
jest.spyOn($.prototype, 'off').mockImplementation(function () {
return this;
});
VersionCheckImage.bindErrorEvent(testContext.imageElement);
expect($.prototype.off).toHaveBeenCalledWith('error');
expect($.prototype.on).toHaveBeenCalledWith('error', expect.any(Function));
});
it('hides the imageElement on error', () => {
jest.spyOn($.prototype, 'hide').mockImplementation(() => {});
VersionCheckImage.bindErrorEvent(testContext.imageElement);
testContext.imageElement.trigger('error');
expect($.prototype.hide).toHaveBeenCalled();
});
});
});
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import flushPromises from 'helpers/flush_promises';
import axios from '~/lib/utils/axios_utils';
import GitlabVersionCheck from '~/vue_shared/components/gitlab_version_check.vue';
describe('GitlabVersionCheck', () => {
let wrapper;
let mock;
const defaultResponse = {
code: 200,
res: { severity: 'success' },
};
const createComponent = (mockResponse) => {
const response = {
...defaultResponse,
...mockResponse,
};
mock = new MockAdapter(axios);
mock.onGet().replyOnce(response.code, response.res);
wrapper = shallowMount(GitlabVersionCheck);
};
afterEach(() => {
wrapper.destroy();
mock.restore();
});
const findGlBadge = () => wrapper.findComponent(GlBadge);
describe('template', () => {
describe.each`
description | mockResponse | renders
${'successful but null'} | ${{ code: 200, res: null }} | ${false}
${'successful and valid'} | ${{ code: 200, res: { severity: 'success' } }} | ${true}
${'an error'} | ${{ code: 500, res: null }} | ${false}
`('version_check.json response', ({ description, mockResponse, renders }) => {
describe(`is ${description}`, () => {
beforeEach(async () => {
createComponent(mockResponse);
await flushPromises(); // Ensure we wrap up the axios call
});
it(`does${renders ? '' : ' not'} render GlBadge`, () => {
expect(findGlBadge().exists()).toBe(renders);
});
});
});
describe.each`
mockResponse | expectedUI
${{ code: 200, res: { severity: 'success' } }} | ${{ title: 'Up to date', variant: 'success' }}
${{ code: 200, res: { severity: 'warning' } }} | ${{ title: 'Update available', variant: 'warning' }}
${{ code: 200, res: { severity: 'danger' } }} | ${{ title: 'Update ASAP', variant: 'danger' }}
`('badge ui', ({ mockResponse, expectedUI }) => {
describe(`when response is ${mockResponse.res.severity}`, () => {
beforeEach(async () => {
createComponent(mockResponse);
await flushPromises(); // Ensure we wrap up the axios call
});
it(`title is ${expectedUI.title}`, () => {
expect(findGlBadge().text()).toBe(expectedUI.title);
});
it(`variant is ${expectedUI.variant}`, () => {
expect(findGlBadge().attributes('variant')).toBe(expectedUI.variant);
});
});
});
});
});
...@@ -3,33 +3,34 @@ ...@@ -3,33 +3,34 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe VersionCheckHelper do RSpec.describe VersionCheckHelper do
describe '#version_status_badge' do let_it_be(:user) { create(:user) }
it 'returns nil if not dev environment and not enabled' do
stub_rails_env('development')
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { false }
expect(helper.version_status_badge).to be(nil) describe '#show_version_check?' do
end describe 'return conditions' do
where(:enabled, :consent, :is_admin, :result) do
context 'when production and enabled' do [
before do [false, false, false, false],
stub_rails_env('production') [false, false, true, false],
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { true } [false, true, false, false],
allow(VersionCheck).to receive(:image_url) { 'https://version.host.com/check.svg?gitlab_info=xxx' } [false, true, true, false],
[true, false, false, false],
[true, false, true, true],
[true, true, false, false],
[true, true, true, false]
]
end end
it 'returns an image tag' do with_them do
expect(helper.version_status_badge).to start_with('<img') before do
end stub_application_setting(version_check_enabled: enabled)
allow(User).to receive(:single_user).and_return(double(user, requires_usage_stats_consent?: consent))
it 'has a js prefixed css class' do allow(helper).to receive(:current_user).and_return(user)
expect(helper.version_status_badge) allow(user).to receive(:can_read_all_resources?).and_return(is_admin)
.to match(/class="js-version-status-badge lazy"/) end
end
it 'has a VersionCheck image_url as the src' do it 'returns correct results' do
expect(helper.version_status_badge) expect(helper.show_version_check?).to eq result
.to include(%{src="https://version.host.com/check.svg?gitlab_info=xxx"}) end
end end
end end
end end
......
...@@ -3,12 +3,6 @@ ...@@ -3,12 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe VersionCheck do RSpec.describe VersionCheck do
describe '.image_url' do
it 'returns the correct URL' do
expect(described_class.image_url).to match(%r{\A#{Regexp.escape(described_class.host)}/check\.svg\?gitlab_info=\w+})
end
end
describe '.url' do describe '.url' do
it 'returns the correct URL' do it 'returns the correct URL' do
expect(described_class.url).to match(%r{\A#{Regexp.escape(described_class.host)}/check\.json\?gitlab_info=\w+}) expect(described_class.url).to match(%r{\A#{Regexp.escape(described_class.host)}/check\.json\?gitlab_info=\w+})
......
...@@ -459,6 +459,11 @@ RSpec.configure do |config| ...@@ -459,6 +459,11 @@ RSpec.configure do |config|
end end
end end
# Ensures that any Javascript script that tries to make the external VersionCheck API call skips it and returns a response
config.before(:each, :js) do
allow_any_instance_of(VersionCheck).to receive(:response).and_return({ "severity" => "success" })
end
config.after(:each, :silence_stdout) do config.after(:each, :silence_stdout) do
$stdout = STDOUT $stdout = STDOUT
end end
......
...@@ -53,11 +53,14 @@ RSpec.describe 'admin/dashboard/index.html.haml' do ...@@ -53,11 +53,14 @@ RSpec.describe 'admin/dashboard/index.html.haml' do
expect(rendered).not_to have_content "Users over License" expect(rendered).not_to have_content "Users over License"
end end
it 'links to the GitLab Changelog' do describe 'when show_version_check? is true' do
stub_application_setting(version_check_enabled: true) before do
allow(view).to receive(:show_version_check?).and_return(true)
render render
end
expect(rendered).to have_link(href: 'https://gitlab.com/gitlab-org/gitlab/-/blob/master/CHANGELOG.md') it 'renders the version check badge' do
expect(rendered).to have_selector('.js-gitlab-version-check')
end
end end
end end
...@@ -76,7 +76,6 @@ RSpec.describe 'help/index' do ...@@ -76,7 +76,6 @@ RSpec.describe 'help/index' do
def stub_helpers def stub_helpers
allow(view).to receive(:markdown).and_return('') allow(view).to receive(:markdown).and_return('')
allow(view).to receive(:version_status_badge).and_return('')
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings) allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end 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