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

Create data model and transformers for Organization type #9152

Merged
merged 10 commits into from
Sep 13, 2024
4 changes: 2 additions & 2 deletions src/auth/authTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import { type UserPartner } from "@/data/model/UserPartner";
import { type PartnerPrincipal } from "@/data/model/PartnerPrincipal";
import { type OrganizationTheme } from "@/data/model/OrganizationTheme";
import { type ControlRoom } from "@/data/model/ControlRoom";
import { type UserRole } from "@/types/contract";
import { type UserMilestone } from "@/data/model/UserMilestone";
import { type LegacyUserRole } from "@/data/model/UserRole";

export type AuthSharing = "private" | "shared" | "built-in";

Expand Down Expand Up @@ -182,7 +182,7 @@ export type AuthUserOrganization = {
/**
* The user's role within the organization.
*/
role: UserRole;
role: LegacyUserRole;
/**
* The organization's brick scope, or null if not set.
*/
Expand Down
10 changes: 7 additions & 3 deletions src/auth/authUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ import {
} from "@/auth/authTypes";
import { type Me } from "@/data/model/Me";
import selectAuthUserOrganizations from "@/auth/selectAuthUserOrganizations";
import { UserRole } from "@/types/contract";
import {
readReduxStorage,
validateReduxStorageKey,
} from "@/utils/storageUtils";
import { type Nullishable } from "@/utils/nullishUtils";
import { anonAuth } from "@/auth/authConstants";
import { LegacyUserRole } from "@/data/model/UserRole";

const AUTH_SLICE_STORAGE_KEY = validateReduxStorageKey("persist:authOptions");

Expand Down Expand Up @@ -122,6 +122,10 @@ export function selectExtensionAuthState({
* Returns true if the role corresponds to permission to edit a package.
* See https://docs.pixiebrix.com/managing-teams/access-control/roles
*/
export function isPackageEditorRole(role: UserRole): boolean {
return [UserRole.admin, UserRole.manager, UserRole.developer].includes(role);
export function isPackageEditorRole(role: LegacyUserRole): boolean {
return [
LegacyUserRole.admin,
LegacyUserRole.manager,
LegacyUserRole.developer,
].includes(role);
}
4 changes: 2 additions & 2 deletions src/auth/selectAuthUserOrganizations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { type AuthUserOrganization } from "@/auth/authTypes";
import { type Nullishable } from "@/utils/nullishUtils";
import { type MeOrganizationMembership } from "@/data/model/MeOrganizationMembership";
import { convertToLegacyUserRole } from "@/data/model/UserOrganizationMembershipRole";
import { convertToUserRole } from "@/data/model/UserRole";

// Export this function because it's used in both the Extension and the App
export default function selectAuthUserOrganizations(
Expand All @@ -40,7 +40,7 @@ export default function selectAuthUserOrganizations(
id: organizationId,
name: organizationName,
control_room: organizationControlRoom,
role: convertToLegacyUserRole(userOrganizationRole),
role: convertToUserRole(userOrganizationRole),
scope: organizationScope,
isDeploymentManager: meUserIsDeploymentManager,
}),
Expand Down
18 changes: 14 additions & 4 deletions src/components/fields/schemaFields/widgets/DatabaseCreateModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ import {
import SelectWidget from "@/components/form/widgets/SelectWidget";
import DatabaseGroupSelect from "@/components/fields/schemaFields/DatabaseGroupSelect";
import notify from "@/utils/notify";
import { type Organization, UserRole } from "@/types/contract";
import { type UUID } from "@/types/stringTypes";
import { validateUUID } from "@/types/helpers";
import { type Organization } from "@/data/model/Organization";
import { UserRole } from "@/data/model/UserRole";

type DatabaseCreateModalProps = {
show: boolean;
Expand Down Expand Up @@ -70,10 +71,19 @@ const initialValues: DatabaseConfig = {

function getOrganizationOptions(organizations: Organization[]) {
const organizationOptions = (organizations ?? [])
.filter((organization) => organization.role === UserRole.admin)
.filter(
(organization) =>
organization.memberships?.some(
(member) =>
// If the current user is an admin of the organization, then all of the members are listed for the organization
// Otherwise, only the current user is listed for the organization
// So if any listed member is an admin, the current user is an admin
member.role === UserRole.admin,
),
)
.map((organization) => ({
label: organization.name,
value: organization.id,
label: organization.organizationName,
value: organization.organizationId,
}));

const personalDbOption = {
Expand Down
8 changes: 4 additions & 4 deletions src/components/form/widgets/RegistryIdWidget.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ import { render, screen } from "@/pageEditor/testHelpers";
import { authActions } from "@/auth/authSlice";
import userEvent from "@testing-library/user-event";
import { partition } from "lodash";
import { UserRole } from "@/types/contract";
import { LegacyUserRole } from "@/data/model/UserRole";
import { validateRegistryId } from "@/types/helpers";
import {
authStateFactory,
organizationStateFactory,
} from "@/testUtils/factories/authFactories";

const editorRoles = new Set<number>([
UserRole.admin,
UserRole.developer,
UserRole.manager,
LegacyUserRole.admin,
LegacyUserRole.developer,
LegacyUserRole.manager,
]);

describe("RegistryIdWidget", () => {
Expand Down
8 changes: 4 additions & 4 deletions src/components/form/widgets/RegistryIdWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ import { type RegistryId } from "@/types/registryTypes";
import { Form } from "react-bootstrap";
import styles from "./RegistryIdWidget.module.scss";
import { type StylesConfig } from "react-select";
import { UserRole } from "@/types/contract";
import { LegacyUserRole } from "@/data/model/UserRole";

import { getScopeAndId } from "@/utils/registryUtils";
import useAsyncEffect from "use-async-effect";
import { assertNotNullish } from "@/utils/nullishUtils";

const editorRoles = new Set<number>([
UserRole.admin,
UserRole.developer,
UserRole.manager,
LegacyUserRole.admin,
LegacyUserRole.developer,
LegacyUserRole.manager,
]);

const emptyObject = {} as const;
Expand Down
12 changes: 5 additions & 7 deletions src/data/model/MeOrganizationMembership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

import { type UUID } from "@/types/stringTypes";
import {
transformUserOrganizationMembershipRoleResponse,
type UserOrganizationMembershipRole,
} from "@/data/model/UserOrganizationMembershipRole";
transformUserRoleResponse,
type UserRoleType,
} from "@/data/model/UserRole";
import {
type ControlRoom,
transformControlRoomResponse,
Expand All @@ -33,7 +33,7 @@ export type MeOrganizationMembership = {
*/
organizationId: UUID;
organizationName: string;
userOrganizationRole: UserOrganizationMembershipRole;
userOrganizationRole: UserRoleType;
/**
* Whether the (parent) user is a manager of one or more team deployments for the organization
*/
Expand All @@ -48,9 +48,7 @@ export function transformMeOrganizationMembershipResponse(
const membership: MeOrganizationMembership = {
organizationId: validateUUID(response.organization),
organizationName: response.organization_name,
userOrganizationRole: transformUserOrganizationMembershipRoleResponse(
response.role,
),
userOrganizationRole: transformUserRoleResponse(response.role),
meUserIsDeploymentManager: response.is_deployment_manager ?? false,
};

Expand Down
91 changes: 91 additions & 0 deletions src/data/model/Organization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import {
transformOrganizationMembershipResponse,
type OrganizationMembership,
} from "@/data/model/OrganizationMemberships";
import {
transformOrganizationThemeResponse,
type OrganizationTheme,
} from "@/data/model/OrganizationTheme";
import {
type UserRoleType,
transformUserRoleResponse,
} from "@/data/model/UserRole";
import { validateUUID } from "@/types/helpers";
import { type Timestamp, type UUID } from "@/types/stringTypes";
import { type components } from "@/types/swagger";

export type Organization = {
/**
* The organization's ID.
*/
organizationId: UUID;
/**
* The organization's name.
*/
organizationName: string;
/**
* The organization's memberships.
*/
memberships: OrganizationMembership[] | null;
/**
* The organization's scope.
*/
scope: string | null;
/**
* The organization's default role.
*/
defaultRole: UserRoleType | null;
/**
* The organization's partner.
*/
partner: string | null;
/**
* The organization's enforce update millis.
*/
enforceUpdateMillis: number | null;
/**
* The organization's UI theme.
*/
theme: OrganizationTheme | null;

trialEndTimestamp: Timestamp | null;
};

export function transformOrganizationResponse(
baseQueryReturnValue: Array<components["schemas"]["Organization"]>,
): Organization[] {
return baseQueryReturnValue.map((apiOrganization) => ({
organizationId: validateUUID(apiOrganization.id),
organizationName: apiOrganization.name,
memberships: transformOrganizationMembershipResponse(
apiOrganization.members,
),
scope: apiOrganization.scope ?? null,
defaultRole: apiOrganization.default_role
? transformUserRoleResponse(apiOrganization.default_role)
: null,
partner: apiOrganization.partner ?? null,
enforceUpdateMillis: apiOrganization.enforce_update_millis ?? null,
theme: apiOrganization.theme
? transformOrganizationThemeResponse(apiOrganization.theme)
: null,
trialEndTimestamp: apiOrganization.trial_end_timestamp ?? null,
}));
}
39 changes: 39 additions & 0 deletions src/data/model/OrganizationMembershipGroups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { type UUID } from "@/types/stringTypes";
import { type components } from "@/types/swagger";
import { type SetRequired } from "type-fest";

export type OrganizationMembershipGroup = {
groupId: UUID;
groupName: string;
};

type Memberships = SetRequired<
components["schemas"]["Organization"],
"members"
>["members"];

export function transformOrganizationMemberGroupsResponse(
groups: Memberships[number]["groups"],
): OrganizationMembershipGroup[] | undefined {
return groups?.map((group) => ({
groupId: group.id,
groupName: group.name,
}));
}
48 changes: 48 additions & 0 deletions src/data/model/OrganizationMembershipUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { validateUUID } from "@/types/helpers";
import { type Timestamp, type UUID } from "@/types/stringTypes";
import { type components } from "@/types/swagger";
import { type SetRequired } from "type-fest";

export type OrganizationMembershipUser = {
userId: UUID;
userName?: string;
userEmail?: string;
serviceAccount?: boolean;
deploymentKeyAccount?: boolean;
dateJoined?: Timestamp;
};

type Memberships = SetRequired<
components["schemas"]["Organization"],
"members"
>["members"];

export function transformOrganizationMemberUserResponse(
user: Memberships[number]["user"],
): OrganizationMembershipUser {
return {
userId: validateUUID(user?.id),
userName: user?.name,
userEmail: user?.email,
serviceAccount: user?.service_account,
deploymentKeyAccount: user?.deployment_key_account,
dateJoined: user?.date_joined,
};
}
Loading
Loading