Commit 78c8851d authored by Robert Hunt's avatar Robert Hunt

Add the compliance framework label to group projects listing

- Add compliance framework to entity
- Add compliance framework label component to group item
- Add compliance framework to store
- Create tests

Changelog: added
MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66819
EE: true
parent f062cb0d
......@@ -28,6 +28,10 @@ export default {
GlLoadingIcon,
GlIcon,
UserAccessRoleBadge,
ComplianceFrameworkLabel: () =>
import(
'ee_component/vue_shared/components/compliance_framework_label/compliance_framework_label.vue'
),
itemCaret,
itemTypeIcon,
itemStats,
......@@ -67,6 +71,9 @@ export default {
hasAvatar() {
return this.group.avatarUrl !== null;
},
hasComplianceFramework() {
return Boolean(this.group.complianceFramework?.name);
},
isGroup() {
return this.group.type === 'group';
},
......@@ -82,6 +89,9 @@ export default {
microdata() {
return this.group.microdata || {};
},
complianceFramework() {
return this.group.complianceFramework;
},
},
methods: {
onClickRowGroup(e) {
......@@ -167,6 +177,13 @@ export default {
<user-access-role-badge v-if="group.permission" class="gl-mt-3">
{{ group.permission }}
</user-access-role-badge>
<compliance-framework-label
v-if="hasComplianceFramework"
class="gl-mt-3"
:name="complianceFramework.name"
:color="complianceFramework.color"
:description="complianceFramework.description"
/>
</div>
<div v-if="group.description" class="description">
<span
......
import { normalizeHeaders, parseIntPagination } from '../../lib/utils/common_utils';
import { isEmpty } from 'lodash';
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
import { getGroupItemMicrodata } from './utils';
export default class GroupsStore {
......@@ -70,7 +71,7 @@ export default class GroupsStore {
? rawGroupItem.subgroup_count
: rawGroupItem.children_count;
return {
const groupItem = {
id: rawGroupItem.id,
name: rawGroupItem.name,
fullName: rawGroupItem.full_name,
......@@ -98,6 +99,16 @@ export default class GroupsStore {
pendingRemoval: rawGroupItem.marked_for_deletion,
microdata: this.showSchemaMarkup ? getGroupItemMicrodata(rawGroupItem) : {},
};
if (!isEmpty(rawGroupItem.compliance_management_framework)) {
groupItem.complianceFramework = {
name: rawGroupItem.compliance_management_framework.name,
color: rawGroupItem.compliance_management_framework.color,
description: rawGroupItem.compliance_management_framework.description,
};
}
return groupItem;
}
removeGroup(group, parentGroup) {
......
......@@ -9,6 +9,16 @@ module EE
expose :marked_for_deletion do |instance|
instance.marked_for_deletion?
end
expose :compliance_management_framework, if: lambda { |_instance, _options| compliance_framework_available? }
end
private
def compliance_framework_available?
return unless project?
object.licensed_feature_available?(:compliance_framework)
end
end
end
import { shallowMount } from '@vue/test-utils';
import ComplianceFrameworkLabel from 'ee_component/vue_shared/components/compliance_framework_label/compliance_framework_label.vue';
import waitForPromises from 'helpers/wait_for_promises';
import GroupFolder from '~/groups/components/group_folder.vue';
import GroupItem from '~/groups/components/group_item.vue';
import { mockParentGroupItem, mockChildren } from '../mock_data';
const createComponent = (props = {}) => {
return shallowMount(GroupItem, {
propsData: {
parentGroup: mockParentGroupItem,
...props,
},
components: { GroupFolder },
});
};
describe('GroupItemComponent', () => {
let wrapper;
const findComplianceFrameworkLabel = () => wrapper.findComponent(ComplianceFrameworkLabel);
afterEach(() => {
wrapper.destroy();
});
describe('Compliance framework label', () => {
it('does not render if the item does not have a compliance framework', async () => {
wrapper = createComponent({ group: mockChildren[0] });
await waitForPromises();
expect(findComplianceFrameworkLabel().exists()).toBe(false);
});
it('renders if the item has a compliance framework', async () => {
const { color, description, name } = mockChildren[1].complianceFramework;
wrapper = createComponent({ group: mockChildren[1] });
await waitForPromises();
expect(findComplianceFrameworkLabel().props()).toStrictEqual({
color,
description,
name,
});
});
});
});
export const mockParentGroupItem = {
id: 55,
name: 'hardware',
description: '',
visibility: 'public',
fullName: 'platform / hardware',
relativePath: '/platform/hardware',
canEdit: true,
type: 'group',
avatarUrl: null,
permission: 'Owner',
editPath: '/groups/platform/hardware/edit',
childrenCount: 3,
leavePath: '/groups/platform/hardware/group_members/leave',
parentId: 54,
memberCount: '1',
projectCount: 1,
subgroupCount: 2,
canLeave: false,
children: [],
isOpen: true,
isChildrenLoading: false,
isBeingRemoved: false,
updatedAt: '2017-04-09T18:40:39.101Z',
};
export const mockChildren = [
{
id: 57,
name: 'bsp',
description: '',
visibility: 'public',
fullName: 'platform / hardware / bsp',
relativePath: '/platform/hardware/bsp',
canEdit: true,
type: 'group',
avatarUrl: null,
permission: 'Owner',
editPath: '/groups/platform/hardware/bsp/edit',
childrenCount: 6,
leavePath: '/groups/platform/hardware/bsp/group_members/leave',
parentId: 55,
memberCount: '1',
projectCount: 4,
subgroupCount: 2,
canLeave: false,
children: [],
isOpen: true,
isChildrenLoading: false,
isBeingRemoved: false,
updatedAt: '2017-04-09T18:40:39.101Z',
complianceFramework: {},
},
{
id: 57,
name: 'bsp',
description: '',
visibility: 'public',
fullName: 'platform / hardware / bsp',
relativePath: '/platform/hardware/bsp',
canEdit: true,
type: 'group',
avatarUrl: null,
permission: 'Owner',
editPath: '/groups/platform/hardware/bsp/edit',
childrenCount: 6,
leavePath: '/groups/platform/hardware/bsp/group_members/leave',
parentId: 55,
memberCount: '1',
projectCount: 4,
subgroupCount: 2,
canLeave: false,
children: [],
isOpen: true,
isChildrenLoading: false,
isBeingRemoved: false,
updatedAt: '2017-04-09T18:40:39.101Z',
complianceFramework: {
name: 'GDPR',
description: 'General Data Protection Regulation',
color: '#009966',
},
},
];
export const mockRawChildren = [
{
id: 57,
name: 'bsp',
description: '',
visibility: 'public',
full_name: 'platform / hardware / bsp',
relative_path: '/platform/hardware/bsp',
can_edit: true,
type: 'group',
avatar_url: null,
permission: 'Owner',
edit_path: '/groups/platform/hardware/bsp/edit',
children_count: 6,
leave_path: '/groups/platform/hardware/bsp/group_members/leave',
parent_id: 55,
number_users_with_delimiter: '1',
project_count: 4,
subgroup_count: 2,
can_leave: false,
children: [],
updated_at: '2017-04-09T18:40:39.101Z',
},
{
id: 57,
name: 'bsp',
description: '',
visibility: 'public',
full_name: 'platform / hardware / bsp',
relative_path: '/platform/hardware/bsp',
can_edit: true,
type: 'group',
avatar_url: null,
permission: 'Owner',
edit_path: '/groups/platform/hardware/bsp/edit',
children_count: 6,
leave_path: '/groups/platform/hardware/bsp/group_members/leave',
parent_id: 55,
number_users_with_delimiter: '1',
project_count: 4,
subgroup_count: 2,
can_leave: false,
children: [],
updated_at: '2017-04-09T18:40:39.101Z',
compliance_management_framework: {
id: 1,
namespace_id: 1,
name: 'GDPR',
description: 'General Data Protection Regulation',
color: '#009966',
pipeline_configuration_full_path: null,
regulated: true,
},
},
];
import GroupsStore from '~/groups/store/groups_store';
import { mockRawChildren } from '../mock_data';
describe('ee/ProjectsStore', () => {
describe('formatGroupItem', () => {
it('without a compliance framework', () => {
const store = new GroupsStore();
const updatedGroupItem = store.formatGroupItem(mockRawChildren[0]);
expect(updatedGroupItem.complianceFramework).toBeUndefined();
});
it('with a compliance framework', () => {
const store = new GroupsStore();
const updatedGroupItem = store.formatGroupItem(mockRawChildren[1]);
expect(updatedGroupItem.complianceFramework).toStrictEqual({
name: mockRawChildren[1].compliance_management_framework.name,
color: mockRawChildren[1].compliance_management_framework.color,
description: mockRawChildren[1].compliance_management_framework.description,
});
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GroupChildEntity do
include ExternalAuthorizationServiceHelpers
include Gitlab::Routing.url_helpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :with_sox_compliance_framework) }
let_it_be(:project_without_compliance_framework) { create(:project) }
let_it_be(:group) { create(:group) }
let(:request) { double('request') }
let(:entity) { described_class.new(object, request: request) }
subject(:json) { entity.as_json }
before do
allow(request).to receive(:current_user).and_return(user)
stub_commonmark_sourcepos_disabled
end
describe 'with compliance framework' do
shared_examples 'does not have the compliance framework' do
it do
expect(json[:compliance_management_framework]).to be_nil
end
end
context 'disabled' do
before do
stub_licensed_features(compliance_framework: false)
end
context 'for a project' do
let(:object) { project }
it_behaves_like 'does not have the compliance framework'
end
context 'for a group' do
let(:object) { group }
it_behaves_like 'does not have the compliance framework'
end
end
describe 'enabled' do
before do
stub_licensed_features(compliance_framework: true)
end
context 'for a project' do
let(:object) { project }
it 'has the compliance framework' do
expect(json[:compliance_management_framework]['name']).to eq('SOX')
end
end
context 'for a project without a compliance framework' do
let(:object) { project_without_compliance_framework }
it_behaves_like 'does not have the compliance framework'
end
context 'for a group' do
let(:object) { group }
it_behaves_like 'does not have the compliance framework'
end
end
end
end
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import GroupFolder from '~/groups/components/group_folder.vue';
import GroupItem from '~/groups/components/group_item.vue';
import ItemActions from '~/groups/components/item_actions.vue';
......@@ -22,8 +22,7 @@ describe('GroupItemComponent', () => {
beforeEach(() => {
wrapper = createComponent();
return Vue.nextTick();
return waitForPromises();
});
afterEach(() => {
......
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