Commit 262aa940 authored by Mark Florian's avatar Mark Florian

Merge branch '199066-add-correct-anchor-link-to-new-policy-tab' into 'master'

Link license management button to license compliance policies section

See merge request gitlab-org/gitlab!30344
parents 91dc8ac6 66c54a54
...@@ -16,6 +16,7 @@ import DetectedLicensesTable from './detected_licenses_table.vue'; ...@@ -16,6 +16,7 @@ import DetectedLicensesTable from './detected_licenses_table.vue';
import PipelineInfo from './pipeline_info.vue'; import PipelineInfo from './pipeline_info.vue';
import LicenseManagement from 'ee/vue_shared/license_compliance/license_management.vue'; import LicenseManagement from 'ee/vue_shared/license_compliance/license_management.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { getLocationHash } from '~/lib/utils/url_utility';
export default { export default {
name: 'LicenseComplianceApp', name: 'LicenseComplianceApp',
...@@ -45,9 +46,10 @@ export default { ...@@ -45,9 +46,10 @@ export default {
}, },
data() { data() {
return { return {
tabIndex: 0, tabIndex: this.activeTabIndex(),
}; };
}, },
tabNames: ['licenses', 'policies'],
computed: { computed: {
...mapState(LICENSE_LIST, ['initialized', 'licenses', 'reportInfo', 'listTypes', 'pageInfo']), ...mapState(LICENSE_LIST, ['initialized', 'licenses', 'reportInfo', 'listTypes', 'pageInfo']),
...mapState(LICENSE_MANAGEMENT, ['managedLicenses']), ...mapState(LICENSE_MANAGEMENT, ['managedLicenses']),
...@@ -68,11 +70,25 @@ export default { ...@@ -68,11 +70,25 @@ export default {
return this.tabIndex === 0; return this.tabIndex === 0;
}, },
}, },
watch: {
tabIndex: {
handler(newTabIndex) {
window.location.hash = this.$options.tabNames[newTabIndex];
},
// this ensures that the hash will be set on creation if it is empty
immediate: true,
},
},
created() { created() {
this.fetchLicenses(); this.fetchLicenses();
}, },
methods: { methods: {
...mapActions(LICENSE_LIST, ['fetchLicenses']), ...mapActions(LICENSE_LIST, ['fetchLicenses']),
activeTabIndex() {
const activeTabIndex = this.$options.tabNames.indexOf(getLocationHash());
return activeTabIndex !== -1 ? activeTabIndex : 0;
},
}, },
}; };
</script> </script>
...@@ -121,18 +137,18 @@ export default { ...@@ -121,18 +137,18 @@ export default {
<!-- TODO: Remove feature flag --> <!-- TODO: Remove feature flag -->
<template v-if="hasLicensePolicyList"> <template v-if="hasLicensePolicyList">
<gl-tabs v-model="tabIndex" content-class="pt-0"> <gl-tabs v-model="tabIndex" content-class="pt-0">
<gl-tab> <gl-tab data-testid="licensesTab">
<template #title> <template #title>
{{ s__('Licenses|Detected in Project') }} <span data-testid="licensesTabTitle">{{ s__('Licenses|Detected in Project') }}</span>
<gl-badge pill>{{ licenseCount }}</gl-badge> <gl-badge pill>{{ licenseCount }}</gl-badge>
</template> </template>
<detected-licenses-table /> <detected-licenses-table />
</gl-tab> </gl-tab>
<gl-tab> <gl-tab data-testid="policiesTab">
<template #title> <template #title>
{{ s__('Licenses|Policies') }} <span data-testid="policiesTabTitle">{{ s__('Licenses|Policies') }}</span>
<gl-badge pill>{{ policyCount }}</gl-badge> <gl-badge pill>{{ policyCount }}</gl-badge>
</template> </template>
......
...@@ -35,7 +35,7 @@ module EE ...@@ -35,7 +35,7 @@ module EE
end end
def license_management_settings_path(project) def license_management_settings_path(project)
project_settings_ci_cd_path(project, anchor: 'js-license-management') project_licenses_path(project, anchor: 'policies')
end end
def vulnerability_path(entity, *args) def vulnerability_path(entity, *args)
......
---
title: Link license management button to license compliance policies section
merge_request: 30344
author:
type: changed
...@@ -4,6 +4,7 @@ import Vuex from 'vuex'; ...@@ -4,6 +4,7 @@ import Vuex from 'vuex';
import { GlEmptyState, GlLoadingIcon, GlTab, GlTabs, GlAlert, GlBadge } from '@gitlab/ui'; import { GlEmptyState, GlLoadingIcon, GlTab, GlTabs, GlAlert, GlBadge } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import setWindowLocation from 'helpers/set_window_location_helper';
import { REPORT_STATUS } from 'ee/license_compliance/store/modules/list/constants'; import { REPORT_STATUS } from 'ee/license_compliance/store/modules/list/constants';
...@@ -83,6 +84,8 @@ const createComponent = ({ state, props, options }) => { ...@@ -83,6 +84,8 @@ const createComponent = ({ state, props, options }) => {
}); });
}; };
const findByTestId = testId => wrapper.find(`[data-testid="${testId}"]`);
describe('Project Licenses', () => { describe('Project Licenses', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -195,6 +198,50 @@ describe('Project Licenses', () => { ...@@ -195,6 +198,50 @@ describe('Project Licenses', () => {
expect(wrapper.find(PipelineInfo).exists()).toBe(true); expect(wrapper.find(PipelineInfo).exists()).toBe(true);
}); });
describe.each`
givenLocationHash | expectedActiveTab
${'#licenses'} | ${'licenses'}
${'#policies'} | ${'policies'}
`(
'when window.location contains the hash "$givenLocationHash"',
({ givenLocationHash, expectedActiveTab }) => {
const originalLocation = window.location;
beforeEach(() => {
setWindowLocation(`http://foo.com/index${givenLocationHash}`);
createComponent({
state: {
initialized: true,
isLoading: false,
licenses: [
{
name: 'MIT',
classification: LICENSE_APPROVAL_CLASSIFICATION.DENIED,
components: [],
},
],
pageInfo: 1,
},
options: {
provide: {
glFeatures: { licensePolicyList: true },
},
mount: true,
},
});
});
afterEach(() => {
window.location = originalLocation;
});
it(`sets the active tab to be "${expectedActiveTab}"`, () => {
expect(findByTestId(`${expectedActiveTab}Tab`).classes()).toContain('active');
});
},
);
describe('when the tabs are rendered', () => { describe('when the tabs are rendered', () => {
const pageInfo = { const pageInfo = {
total: 1, total: 1,
...@@ -228,6 +275,21 @@ describe('Project Licenses', () => { ...@@ -228,6 +275,21 @@ describe('Project Licenses', () => {
}); });
}); });
it.each`
givenActiveTab | expectedLocationHash
${'policies'} | ${'#policies'}
${'licenses'} | ${'#licenses'}
`(
'sets the location hash to "$expectedLocationHash" when the "$givenTab" tab is activate',
({ givenActiveTab, expectedLocationHash }) => {
findByTestId(`${givenActiveTab}TabTitle`).trigger('click');
return wrapper.vm.$nextTick().then(() => {
expect(window.location.hash).toBe(expectedLocationHash);
});
},
);
it('it renders the correct count in "Detected in project" tab', () => { it('it renders the correct count in "Detected in project" tab', () => {
expect( expect(
wrapper wrapper
......
...@@ -87,6 +87,14 @@ describe EE::GitlabRoutingHelper do ...@@ -87,6 +87,14 @@ describe EE::GitlabRoutingHelper do
end end
end end
describe '#license_management_settings_path' do
it 'generates a path to the license compliance page' do
result = helper.license_management_settings_path(project)
expect(result).to eq('/foo/bar/-/licenses#policies')
end
end
describe '#user_group_saml_omniauth_metadata_path' do describe '#user_group_saml_omniauth_metadata_path' do
subject do subject do
helper.user_group_saml_omniauth_metadata_path(group) helper.user_group_saml_omniauth_metadata_path(group)
......
/**
* setWindowLocation allows for setting `window.location`
* (doing so directly is causing an error in jsdom)
*
* Example usage:
* assert(window.location.hash === undefined);
* setWindowLocation('http://example.com#foo')
* assert(window.location.hash === '#foo');
*
* More information:
* https://github.com/facebook/jest/issues/890
*
* @param url
*/
export default function setWindowLocation(url) {
const parsedUrl = new URL(url);
const newLocationValue = [
'hash',
'host',
'hostname',
'href',
'origin',
'pathname',
'port',
'protocol',
'search',
].reduce(
(location, prop) => ({
...location,
[prop]: parsedUrl[prop],
}),
{},
);
Object.defineProperty(window, 'location', {
value: newLocationValue,
writable: true,
});
}
import setWindowLocation from './set_window_location_helper';
describe('setWindowLocation', () => {
const originalLocation = window.location;
afterEach(() => {
window.location = originalLocation;
});
it.each`
url | property | value
${'https://gitlab.com#foo'} | ${'hash'} | ${'#foo'}
${'http://gitlab.com'} | ${'host'} | ${'gitlab.com'}
${'http://gitlab.org'} | ${'hostname'} | ${'gitlab.org'}
${'http://gitlab.org/foo#bar'} | ${'href'} | ${'http://gitlab.org/foo#bar'}
${'http://gitlab.com'} | ${'origin'} | ${'http://gitlab.com'}
${'http://gitlab.com/foo/bar/baz'} | ${'pathname'} | ${'/foo/bar/baz'}
${'https://gitlab.com'} | ${'protocol'} | ${'https:'}
${'http://gitlab.com#foo'} | ${'protocol'} | ${'http:'}
${'http://gitlab.com:8080'} | ${'port'} | ${'8080'}
${'http://gitlab.com?foo=bar&bar=foo'} | ${'search'} | ${'?foo=bar&bar=foo'}
`(
'sets "window.location.$property" to be "$value" when called with: "$url"',
({ url, property, value }) => {
expect(window.location).toBe(originalLocation);
setWindowLocation(url);
expect(window.location[property]).toBe(value);
},
);
it.each([null, 1, undefined, false, '', 'gitlab.com'])(
'throws an error when called with an invalid url: "%s"',
invalidUrl => {
expect(() => setWindowLocation(invalidUrl)).toThrow(new TypeError('Invalid URL'));
expect(window.location).toBe(originalLocation);
},
);
});
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