Skip to content

Commit

Permalink
Support multiple features declaring same properties (elastic#71106)
Browse files Browse the repository at this point in the history
Co-authored-by: Joe Portner <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
3 people committed Jul 9, 2020
1 parent 95234bc commit 82e6d50
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ const features = ([
},
},
},
{
// feature 4 intentionally delcares the same items as feature 3
id: 'feature_4',
name: 'Feature 4',
navLinkId: 'feature3',
app: ['feature3', 'feature3_app'],
catalogue: ['feature3Entry'],
management: {
kibana: ['indices'],
},
privileges: {
all: {
app: [],
ui: [],
savedObject: {
all: [],
read: [],
},
},
},
},
] as unknown) as Feature[];

const buildCapabilities = () =>
Expand All @@ -73,6 +94,7 @@ const buildCapabilities = () =>
catalogue: {
discover: true,
visualize: false,
feature3Entry: true,
},
management: {
kibana: {
Expand Down Expand Up @@ -217,11 +239,38 @@ describe('capabilitiesSwitcher', () => {
expect(result).toEqual(expectedCapabilities);
});

it('does not disable catalogue, management, or app entries when they are shared with an enabled feature', async () => {
const space: Space = {
id: 'space',
name: '',
disabledFeatures: ['feature_3'],
};

const capabilities = buildCapabilities();

const { switcher } = setup(space);
const request = httpServerMock.createKibanaRequest();
const result = await switcher(request, capabilities);

const expectedCapabilities = buildCapabilities();

// These capabilities are shared by feature_4, which is enabled
expectedCapabilities.navLinks.feature3 = true;
expectedCapabilities.navLinks.feature3_app = true;
expectedCapabilities.catalogue.feature3Entry = true;
expectedCapabilities.management.kibana.indices = true;
// These capabilities are only exposed by feature_3, which is disabled
expectedCapabilities.feature_3.bar = false;
expectedCapabilities.feature_3.foo = false;

expect(result).toEqual(expectedCapabilities);
});

it('can disable everything', async () => {
const space: Space = {
id: 'space',
name: '',
disabledFeatures: ['feature_1', 'feature_2', 'feature_3'],
disabledFeatures: ['feature_1', 'feature_2', 'feature_3', 'feature_4'],
};

const capabilities = buildCapabilities();
Expand Down
44 changes: 33 additions & 11 deletions x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,41 +54,63 @@ function toggleDisabledFeatures(
) {
const disabledFeatureKeys = activeSpace.disabledFeatures;

const disabledFeatures = disabledFeatureKeys
.map((key) => features.find((feature) => feature.id === key))
.filter((feature) => typeof feature !== 'undefined') as Feature[];
const [enabledFeatures, disabledFeatures] = features.reduce(
(acc, feature) => {
if (disabledFeatureKeys.includes(feature.id)) {
return [acc[0], [...acc[1], feature]];
}
return [[...acc[0], feature], acc[1]];
},
[[], []] as [Feature[], Feature[]]
);

const navLinks = capabilities.navLinks;
const catalogueEntries = capabilities.catalogue;
const managementItems = capabilities.management;

const enabledAppEntries = new Set(enabledFeatures.flatMap((ef) => ef.app ?? []));
const enabledCatalogueEntries = new Set(enabledFeatures.flatMap((ef) => ef.catalogue ?? []));
const enabledManagementEntries = enabledFeatures.reduce((acc, feature) => {
const sections = Object.entries(feature.management ?? {});
sections.forEach((section) => {
if (!acc.has(section[0])) {
acc.set(section[0], []);
}
acc.get(section[0])!.push(...section[1]);
});
return acc;
}, new Map<string, string[]>());

for (const feature of disabledFeatures) {
// Disable associated navLink, if one exists
if (feature.navLinkId && navLinks.hasOwnProperty(feature.navLinkId)) {
navLinks[feature.navLinkId] = false;
}

feature.app.forEach((app) => {
if (navLinks.hasOwnProperty(app)) {
const featureNavLinks = feature.navLinkId ? [feature.navLinkId, ...feature.app] : feature.app;
featureNavLinks.forEach((app) => {
if (navLinks.hasOwnProperty(app) && !enabledAppEntries.has(app)) {
navLinks[app] = false;
}
});

// Disable associated catalogue entries
const privilegeCatalogueEntries = feature.catalogue || [];
privilegeCatalogueEntries.forEach((catalogueEntryId) => {
catalogueEntries[catalogueEntryId] = false;
if (!enabledCatalogueEntries.has(catalogueEntryId)) {
catalogueEntries[catalogueEntryId] = false;
}
});

// Disable associated management items
const privilegeManagementSections = feature.management || {};
Object.entries(privilegeManagementSections).forEach(([sectionId, sectionItems]) => {
sectionItems.forEach((item) => {
const enabledManagementEntriesSection = enabledManagementEntries.get(sectionId);
if (
managementItems.hasOwnProperty(sectionId) &&
managementItems[sectionId].hasOwnProperty(item)
) {
managementItems[sectionId][item] = false;
const isEnabledElsewhere = (enabledManagementEntriesSection ?? []).includes(item);
if (!isEnabledElsewhere) {
managementItems[sectionId][item] = false;
}
}
});
});
Expand Down

0 comments on commit 82e6d50

Please sign in to comment.