Skip to content

Commit

Permalink
wip2
Browse files Browse the repository at this point in the history
  • Loading branch information
n1ru4l committed Jan 20, 2025
1 parent 22cfdd7 commit e451d52
Show file tree
Hide file tree
Showing 3 changed files with 963 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -327,17 +327,17 @@ export class OrganizationMembers {
},
};
})
.filter(isNone),
.filter(isSome),
},
};
})
.filter(isNone),
.filter(isSome),
};
}
}

function isNone<T>(input: T | null): input is Exclude<T, null> {
return input == null;
function isSome<T>(input: T | null): input is Exclude<T, null> {
return input != null;
}

const organizationMemberFields = (prefix = sql`"organization_member"`) => sql`
Expand Down
121 changes: 121 additions & 0 deletions packages/web/app/src/components/organization/members/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,22 @@ import {
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Link } from '@/components/ui/link';
import { SubPageLayout, SubPageLayoutHeader } from '@/components/ui/page-content-layout';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { useToast } from '@/components/ui/use-toast';
import { FragmentType, graphql, useFragment } from '@/gql';
import { AuthProvider } from '@/gql/graphql';
import { RoleSelector } from './common';
import { MemberInvitationButton } from './invitations';
import { ResourcePicker, ResourceSelection } from './resource-picker';

const OrganizationMemberRoleSwitcher_AssignRoleMutation = graphql(`
mutation OrganizationMemberRoleSwitcher_AssignRoleMutation($input: AssignMemberRoleInput!) {
Expand Down Expand Up @@ -120,6 +123,9 @@ function OrganizationMemberRoleSwitcher(props: {
organizationSlug: organization.slug,
roleId: role.id,
userId: member.user.id,
// resources: {
// allProjects: true,
// },
},
});

Expand Down Expand Up @@ -226,6 +232,7 @@ const OrganizationMemberRow_MemberFragment = graphql(`
isOwner
viewerCanRemove
...OrganizationMemberRoleSwitcher_MemberFragment
...AssignedResources_MemberFragment
}
`);

Expand Down Expand Up @@ -338,6 +345,13 @@ function OrganizationMemberRow(props: {
/>
)}
</td>
<td className="relative py-3 text-center text-sm">
{member.isOwner ? (
'all resources'
) : (
<AssignedResources member={member} organization={organization} />
)}
</td>
<td className="py-3 text-right text-sm">
{member.viewerCanRemove && (
<DropdownMenu>
Expand All @@ -358,6 +372,106 @@ function OrganizationMemberRow(props: {
);
}

const AssignedResources_OrganizationFragment = graphql(`
fragment AssignedResources_OrganizationFragment on Organization {
id
...ResourcePicker_OrganizationFragment
}
`);

const AssignedResources_MemberFragment = graphql(`
fragment AssignedResources_MemberFragment on Member {
id
resourceAssignment {
allProjects
projects {
project {
id
slug
}
targets {
allTargets
targets {
target {
id
slug
}
services {
allServices
services
}
appDeployments {
allAppDeployments
appDeployments
}
}
}
}
}
}
`);

function AssignedResources(props: {
member: FragmentType<typeof AssignedResources_MemberFragment>;
organization: FragmentType<typeof AssignedResources_OrganizationFragment>;
}) {
const member = useFragment(AssignedResources_MemberFragment, props.member);
const organization = useFragment(AssignedResources_OrganizationFragment, props.organization);

const [isOpen, setIsOpen] = useState(false);
const initialSelection = useMemo<ResourceSelection>(() => {
return {
projects:
member.resourceAssignment.allProjects === true
? '*'
: (member.resourceAssignment.projects ?? []).map(record => ({
id: record.project.id,
slug: record.project.slug,
targets:
record.targets.allTargets === true
? '*'
: (record.targets.targets ?? []).map(record => ({
id: record.target.id,
slug: record.target.slug,
appDeployments:
record.appDeployments.allAppDeployments === true
? '*'
: (record.appDeployments.appDeployments ?? []),
services:
record.services.allServices === true
? '*'
: (record.services.services ?? []),
})),
})),
};
}, [member]);

return (
<>
{member.resourceAssignment.allProjects ? (
'all resources'
) : member.resourceAssignment.projects?.length ? (
<>
{member.resourceAssignment.projects.length} project
{member.resourceAssignment.projects.length === 1 ? '' : 's'}
</>
) : (
'none'
)}{' '}
<Dialog open={isOpen} onOpenChange={isOpen => setIsOpen(isOpen)}>
<DialogTrigger asChild>
<Link>manage</Link>
</DialogTrigger>
<DialogContent className="min-w-[800px]">
{isOpen && (
<ResourcePicker initialSelection={initialSelection} organization={organization} />
)}
</DialogContent>
</Dialog>
</>
);
}

const OrganizationMembers_OrganizationFragment = graphql(`
fragment OrganizationMembers_OrganizationFragment on Organization {
id
Expand All @@ -382,6 +496,7 @@ const OrganizationMembers_OrganizationFragment = graphql(`
viewerCanManageInvitations
...OrganizationMemberRoleSwitcher_OrganizationFragment
...MemberInvitationForm_OrganizationFragment
...AssignedResources_OrganizationFragment
}
`);

Expand Down Expand Up @@ -479,6 +594,12 @@ export function OrganizationMembers(props: {
) : null}
</span>
</th>
<th
className="relative w-[300px] cursor-pointer select-none py-3 text-center align-middle text-sm font-semibold"
onClick={() => updateSorting('role')}
>
Projects
</th>
<th className="w-12 py-3 text-right text-sm font-semibold" />
</tr>
</thead>
Expand Down
Loading

0 comments on commit e451d52

Please sign in to comment.