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 VersionCheckImage from '~/version_check_image';
import initGitlabVersionCheck from '~/gitlab_version_check';
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
module VersionCheckHelper
def version_status_badge
return unless Rails.env.production?
return unless Gitlab::CurrentSettings.version_check_enabled
return if User.single_user&.requires_usage_stats_consent?
def show_version_check?
return false unless Gitlab::CurrentSettings.version_check_enabled
return false 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
def link_to_version
......
......@@ -118,9 +118,9 @@
.gl-card-body
%h4
= s_('AdminArea|Components')
- if Gitlab::CurrentSettings.version_check_enabled
- if show_version_check?
.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')
%p
= link_to _('GitLab'), general_admin_application_settings_path
......
......@@ -4,12 +4,15 @@
= markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
%hr
%h1
= default_brand_title
- if user_signed_in?
%span= link_to_version
= version_status_badge
%hr
.gl-display-flex.gl-align-items-flex-end
%h1.gl-mt-5.gl-mb-3
= default_brand_title
- if user_signed_in?
%span= link_to_version
- 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?
%p.slead
......
import { shouldQrtlyReconciliationMount } from 'ee/billings/qrtly_reconciliation';
import initGitlabVersionCheck from '~/gitlab_version_check';
shouldQrtlyReconciliationMount();
initGitlabVersionCheck();
......@@ -16,14 +16,6 @@ class VersionCheck
{ "REFERER": Gitlab.config.gitlab.url }
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
encoded_data = Base64.urlsafe_encode64(data.to_json)
......
......@@ -39116,6 +39116,15 @@ msgstr ""
msgid "Version %{versionNumber} (latest)"
msgstr ""
msgid "VersionCheck|Up to date"
msgstr ""
msgid "VersionCheck|Update ASAP"
msgstr ""
msgid "VersionCheck|Update available"
msgstr ""
msgid "View Documentation"
msgstr ""
......
......@@ -28,21 +28,20 @@ RSpec.describe 'Help Pages' do
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
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')
allow(VersionCheck).to receive(:image_url).and_return('/version-check-url')
sign_in(create(:user))
sign_in(user)
visit help_path
end
it 'has a version check image' do
# Check `data-src` due to lazy image loading
expect(find('.js-version-status-badge', visible: false)['data-src'])
.to end_with('/version-check-url')
it 'renders the version check badge' do
expect(page).to have_selector('.js-gitlab-version-check')
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 @@
require 'spec_helper'
RSpec.describe VersionCheckHelper do
describe '#version_status_badge' do
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 }
let_it_be(:user) { create(:user) }
expect(helper.version_status_badge).to be(nil)
end
context 'when production and enabled' do
before do
stub_rails_env('production')
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { true }
allow(VersionCheck).to receive(:image_url) { 'https://version.host.com/check.svg?gitlab_info=xxx' }
describe '#show_version_check?' do
describe 'return conditions' do
where(:enabled, :consent, :is_admin, :result) do
[
[false, false, false, false],
[false, false, true, false],
[false, true, false, false],
[false, true, true, false],
[true, false, false, false],
[true, false, true, true],
[true, true, false, false],
[true, true, true, false]
]
end
it 'returns an image tag' do
expect(helper.version_status_badge).to start_with('<img')
end
it 'has a js prefixed css class' do
expect(helper.version_status_badge)
.to match(/class="js-version-status-badge lazy"/)
end
with_them do
before do
stub_application_setting(version_check_enabled: enabled)
allow(User).to receive(:single_user).and_return(double(user, requires_usage_stats_consent?: consent))
allow(helper).to receive(:current_user).and_return(user)
allow(user).to receive(:can_read_all_resources?).and_return(is_admin)
end
it 'has a VersionCheck image_url as the src' do
expect(helper.version_status_badge)
.to include(%{src="https://version.host.com/check.svg?gitlab_info=xxx"})
it 'returns correct results' do
expect(helper.show_version_check?).to eq result
end
end
end
end
......
......@@ -3,12 +3,6 @@
require 'spec_helper'
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
it 'returns the correct URL' do
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|
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
$stdout = STDOUT
end
......
......@@ -53,11 +53,14 @@ RSpec.describe 'admin/dashboard/index.html.haml' do
expect(rendered).not_to have_content "Users over License"
end
it 'links to the GitLab Changelog' do
stub_application_setting(version_check_enabled: true)
render
describe 'when show_version_check? is true' do
before do
allow(view).to receive(:show_version_check?).and_return(true)
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
......@@ -76,7 +76,6 @@ RSpec.describe 'help/index' do
def stub_helpers
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)
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