Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature flag for to restrict QuickSight link to sysadmins. (PP-1724) #135

Merged
merged 1 commit into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/businessRules/roleBasedAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ type HasLibraryKeyProps = {
[key: string]: unknown;
};

// If the `quicksightOnlyForSysadmins` feature flag is set, only system
// admins should see the QuickSight link.
export const useMaySeeQuickSightLink = (_: HasLibraryKeyProps): boolean => {
const admin = useAppAdmin();
const onlyForSysAdmins = useAppFeatureFlags().quicksightOnlyForSysadmins;
return !onlyForSysAdmins || admin.isSystemAdmin();
};

// If the `reportsOnlyForSysadmins` feature flag is set, only system admins
// may request inventory reports.
export const useMayRequestInventoryReports = (
Expand Down
3 changes: 3 additions & 0 deletions src/components/LibraryStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from "react";
import { LibraryStatistics } from "../interfaces";
import {
useMayRequestInventoryReports,
useMaySeeQuickSightLink,
useMayViewCollectionBarChart,
} from "../businessRules/roleBasedAccess";
import StatsTotalCirculationsGroup from "./StatsTotalCirculationsGroup";
Expand Down Expand Up @@ -45,6 +46,7 @@ const LibraryStats = ({ stats, library }: LibraryStatsProps) => {
const inventoryReportRequestEnabled = useMayRequestInventoryReports({
library,
});
const quicksightLinkEnabled = useMaySeeQuickSightLink({ library });
const quicksightPageUrl = useAppContext().quicksightPagePath;

let statsLayoutClass: string, dashboardTitle: string, implementation: string;
Expand Down Expand Up @@ -74,6 +76,7 @@ const LibraryStats = ({ stats, library }: LibraryStatsProps) => {
<StatsUsageReportsGroup
library={library}
inventoryReportsEnabled={inventoryReportRequestEnabled}
quicksightLinkEnabled={quicksightLinkEnabled}
usageDataTarget="_blank" // open in new tab or window
usageDataHref={quicksightPageUrl}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/StatsCollectionsGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const StatsCollectionsGroup = ({
}: Props) => {
const content =
collections.length === 0 ? (
<span className="no-collections">No associated collections.</span>
<span className="no-content">No associated collections.</span>
) : showBarChart ? (
<StatsCollectionsBarChart collections={collections} />
) : (
Expand Down
35 changes: 22 additions & 13 deletions src/components/StatsUsageReportsGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Props = {
heading?: string;
description?: string;
inventoryReportsEnabled: boolean;
quicksightLinkEnabled: boolean;
library?: string;
usageDataHref?: string;
usageDataLabel?: string;
Expand All @@ -24,6 +25,7 @@ const StatsUsageReportsGroup = ({
usageDataLabel = "View Usage",
usageDataTarget = "_self",
inventoryReportsEnabled,
quicksightLinkEnabled,
library = undefined,
}: Props) => {
const [showReportForm, setShowReportForm] = useState(false);
Expand All @@ -41,6 +43,11 @@ const StatsUsageReportsGroup = ({
<li>
<StatsGroup heading={heading} description={description}>
<>
{!inventoryReportsEnabled && !quicksightLinkEnabled && (
<span className="no-content">
Usage reporting is not available.
</span>
)}
{inventoryReportsEnabled && library && (
<>
<Button
Expand All @@ -63,19 +70,21 @@ const StatsUsageReportsGroup = ({
</>
</StatsGroup>
</li>
<li>
<div className="stat-link">
<a
href={usageDataHref}
target={usageDataTarget}
rel="noopener noreferrer"
>
{usageDataLabel}
</a>
&nbsp;&nbsp;
<i className="fa fa-external-link" />
</div>
</li>
{quicksightLinkEnabled && (
<li>
<div className="stat-link">
<a
href={usageDataHref}
target={usageDataTarget}
rel="noopener noreferrer"
>
{usageDataLabel}
</a>
&nbsp;&nbsp;
<i className="fa fa-external-link" />
</div>
</li>
)}
</ul>
</>
);
Expand Down
1 change: 1 addition & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface TestingFlags {
export interface FeatureFlags {
enableAutoList?: boolean;
reportsOnlyForSysadmins?: boolean;
quicksightOnlyForSysadmins?: boolean;
}

export interface Navigate {
Expand Down
11 changes: 6 additions & 5 deletions src/stylesheets/stats.scss
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,13 @@
margin: 0;
overflow-wrap: normal;
}
}

.no-collections {
margin: 10px;
font-style: italic;
color: $medium-dark-gray;
}
.no-content {
margin: 10px;
font-style: italic;
font-weight: bolder;
color: $medium-dark-gray;
}

.stat-group-description {
Expand Down
1 change: 1 addition & 0 deletions src/utils/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import { FeatureFlags } from "../interfaces";
export const defaultFeatureFlags: FeatureFlags = {
enableAutoList: true,
reportsOnlyForSysadmins: true,
quicksightOnlyForSysadmins: true,
};
93 changes: 93 additions & 0 deletions tests/jest/businessRules/roleBasedAccess.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ContextProviderProps } from "../../../src/components/ContextProvider";
import { ConfigurationSettings, FeatureFlags } from "../../../src/interfaces";
import {
useMayRequestInventoryReports,
useMaySeeQuickSightLink,
useMayViewCollectionBarChart,
} from "../../../src/businessRules/roleBasedAccess";

Expand Down Expand Up @@ -116,6 +117,98 @@ describe("Business rules for role-based access", () => {
});
});

describe("controls access to the quicksight link", () => {
const testAccess = (
expectedResult: boolean,
config: Partial<ConfigurationSettings>
) => {
const wrapper = setupWrapper(config);
const { result } = renderHook(
() => useMaySeeQuickSightLink({ library: libraryMatch }),
{ wrapper }
);
expect(result.current).toBe(expectedResult);
};

it("restricts access to only sysadmins, if the restriction feature flag is true", () => {
const featureFlags: FeatureFlags = { quicksightOnlyForSysadmins: true };

testAccess(true, { roles: [{ role: "system" }], featureFlags });

testAccess(false, { roles: [{ role: "manager-all" }], featureFlags });
testAccess(false, { roles: [{ role: "librarian-all" }], featureFlags });

testAccess(false, {
roles: [{ role: "manager", library: libraryMatch }],
featureFlags,
});
testAccess(false, {
roles: [{ role: "manager", library: libraryMismatch }],
featureFlags,
});
testAccess(false, {
roles: [{ role: "librarian", library: libraryMatch }],
featureFlags,
});
testAccess(false, {
roles: [{ role: "librarian", library: libraryMismatch }],
featureFlags,
});
});

it("allows all users, if the restriction feature flag is is false", () => {
const featureFlags: FeatureFlags = { quicksightOnlyForSysadmins: false };

testAccess(true, { roles: [{ role: "system" }], featureFlags });

testAccess(true, { roles: [{ role: "manager-all" }], featureFlags });
testAccess(true, { roles: [{ role: "librarian-all" }], featureFlags });

testAccess(true, {
roles: [{ role: "manager", library: libraryMatch }],
featureFlags,
});
testAccess(true, {
roles: [{ role: "manager", library: libraryMismatch }],
featureFlags,
});
testAccess(true, {
roles: [{ role: "librarian", library: libraryMatch }],
featureFlags,
});
testAccess(true, {
roles: [{ role: "librarian", library: libraryMismatch }],
featureFlags,
});
});

it("allows all users, if the restriction feature flag is not set", () => {
const featureFlags: FeatureFlags = {};

testAccess(true, { roles: [{ role: "system" }], featureFlags });

testAccess(true, { roles: [{ role: "manager-all" }], featureFlags });
testAccess(true, { roles: [{ role: "librarian-all" }], featureFlags });

testAccess(true, {
roles: [{ role: "manager", library: libraryMatch }],
featureFlags,
});
testAccess(true, {
roles: [{ role: "manager", library: libraryMismatch }],
featureFlags,
});
testAccess(true, {
roles: [{ role: "librarian", library: libraryMatch }],
featureFlags,
});
testAccess(true, {
roles: [{ role: "librarian", library: libraryMismatch }],
featureFlags,
});
});
});

describe("controls access to the collection statistics barchart", () => {
const testAccess = (
expectedResult: boolean,
Expand Down
47 changes: 47 additions & 0 deletions tests/jest/components/Stats.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,53 @@ describe("Dashboard Statistics", () => {
expect(renderFor(false, managerAll)).not.toBeNull();
expect(renderFor(false, librarianAll)).not.toBeNull();
});

it("shows quicksight link only for sysadmins, if sysadmin-only flag set", () => {
const fakeQuickSightHref = "https://example.com/fakeQS";

// We'll use this function to test multiple scenarios.
const renderFor = (
onlySysadmins: boolean,
roles: { role: string; library?: string }[]
) => {
const contextProviderProps: Partial<ContextProviderProps> = {
featureFlags: { quicksightOnlyForSysadmins: onlySysadmins },
roles,
quicksightPagePath: fakeQuickSightHref,
};
const {
container,
getByRole,
queryByRole,
queryByText,
} = renderWithProviders(<Stats library={sampleLibraryKey} />, {
contextProviderProps,
});

// We should always render a Usage reports group when a library is specified.
getByRole("heading", {
level: 3,
name: statGroupToHeading.usageReports,
});
const usageReportLink = queryByRole("link", { name: /View Usage/i });
if (usageReportLink) {
expect(usageReportLink).toHaveAttribute("href", fakeQuickSightHref);
}

// Clean up the container after each render.
document.body.removeChild(container);
return usageReportLink;
};

// If the feature flag is set, the link should be visible only to sysadmins.
expect(renderFor(true, systemAdmin)).not.toBeNull();
expect(renderFor(true, managerAll)).toBeNull();
expect(renderFor(true, librarianAll)).toBeNull();
// If the feature flag is false, the button should be visible to all users.
expect(renderFor(false, systemAdmin)).not.toBeNull();
expect(renderFor(false, managerAll)).not.toBeNull();
expect(renderFor(false, librarianAll)).not.toBeNull();
});
});

describe("charting - custom tooltip", () => {
Expand Down