Skip to content

Commit

Permalink
Merge pull request #2296 from quantified-uncertainty/group-improvements
Browse files Browse the repository at this point in the history
Group improvements
  • Loading branch information
berekuk authored Sep 30, 2023
2 parents c73af0d + eeb021b commit 7f70aa7
Show file tree
Hide file tree
Showing 55 changed files with 1,198 additions and 276 deletions.
5 changes: 5 additions & 0 deletions .changeset/lovely-dolphins-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@quri/ui": patch
---

Improved Dropdown styles
5 changes: 5 additions & 0 deletions .changeset/selfish-llamas-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@quri/ui": patch
---

`PlusIcon`
4 changes: 2 additions & 2 deletions packages/hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"immutable": "^4.3.1",
"invariant": "^2.2.4",
"lodash": "^4.17.21",
"next": "^13.5.2",
"next-auth": "^4.22.1",
"next": "^13.5.3",
"next-auth": "^4.23.1",
"nodemailer": "^6.9.3",
"pako": "^2.1.0",
"react": "^18.2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "ModelRevision" ADD COLUMN "authorId" TEXT;

-- AddForeignKey
ALTER TABLE "ModelRevision" ADD CONSTRAINT "ModelRevision_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
17 changes: 11 additions & 6 deletions packages/hub/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,13 @@ model User {
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
memberships UserGroupMembership[]
invites GroupInvite[]
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
memberships UserGroupMembership[]
invites GroupInvite[]
modelRevisions ModelRevision[]
// Usernames are stored through Owner table, because they share the same namespace with Groups.
// Users don't initially have an Owner entry, but are prompted to pick a slug and create one.
Expand Down Expand Up @@ -199,6 +200,10 @@ model ModelRevision {
model Model @relation("Revisions", fields: [modelId], references: [id], onDelete: Cascade)
modelId String
// optional until we populate old rows after migration
author User? @relation(fields: [authorId], references: [id])
authorId String?
contentType ModelType
squiggleSnippet SquiggleSnippet? @relation(fields: [contentId], references: [id], map: "squiggleSnippet_contentId")
Expand Down
24 changes: 13 additions & 11 deletions packages/hub/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ type Group implements Node & Owner {
updatedAtTimestamp: Float!
}

type GroupConnection {
edges: [GroupEdge!]!
pageInfo: PageInfo!
}

type GroupEdge {
cursor: String!
node: Group!
}

interface GroupInvite {
group: Group!
id: ID!
Expand Down Expand Up @@ -140,6 +150,7 @@ type ModelEdge {
}

type ModelRevision implements Node {
author: User
content: ModelContent!
createdAtTimestamp: Float!
forRelativeValues(input: ModelRevisionForRelativeValuesInput): RelativeValuesExport
Expand Down Expand Up @@ -368,7 +379,7 @@ type PageInfo {
type Query {
globalStatistics: GlobalStatistics!
group(slug: String!): QueryGroupResult!
groups(after: String, before: String, first: Int, input: GroupsQueryInput, last: Int): QueryGroupsConnection!
groups(after: String, before: String, first: Int, input: GroupsQueryInput, last: Int): GroupConnection!
me: Me!
model(input: QueryModelInput!): QueryModelResult!
models(after: String, before: String, first: Int, last: Int): ModelConnection!
Expand All @@ -383,16 +394,6 @@ type Query {

union QueryGroupResult = BaseError | Group | NotFoundError

type QueryGroupsConnection {
edges: [QueryGroupsConnectionEdge!]!
pageInfo: PageInfo!
}

type QueryGroupsConnectionEdge {
cursor: String!
node: Group!
}

input QueryModelInput {
owner: String!
slug: String!
Expand Down Expand Up @@ -556,6 +557,7 @@ type UpdateSquiggleSnippetResult {
}

type User implements Node & Owner {
groups(after: String, before: String, first: Int, last: Int): GroupConnection!
id: ID!
models(after: String, before: String, first: Int, last: Int): ModelConnection!
relativeValuesDefinitions(after: String, before: String, first: Int, last: Int): RelativeValuesDefinitionConnection!
Expand Down
8 changes: 4 additions & 4 deletions packages/hub/src/app/ClientApp.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use client";

import { Session } from "next-auth";
import { SessionProvider } from "next-auth/react";
import { FC, PropsWithChildren } from "react";
import { RelayEnvironmentProvider } from "react-relay";

import { RootLayout } from "@/components/layout/RootLayout";
import { getCurrentEnvironment } from "@/relay/environment";
import { WithToasts } from "@quri/ui";

import { getCurrentEnvironment } from "@/relay/environment";
import { ErrorBoundary } from "@/components/ErrorBoundary";

export const ClientApp: FC<PropsWithChildren<{ session: Session | null }>> = ({
session,
children,
Expand All @@ -19,7 +19,7 @@ export const ClientApp: FC<PropsWithChildren<{ session: Session | null }>> = ({
<SessionProvider session={session}>
<RelayEnvironmentProvider environment={environment}>
<WithToasts>
<RootLayout>{children}</RootLayout>
<ErrorBoundary>{children}</ErrorBoundary>
</WithToasts>
</RelayEnvironmentProvider>
</SessionProvider>
Expand Down
58 changes: 58 additions & 0 deletions packages/hub/src/app/RootLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client";
import { clsx } from "clsx";
import { Session } from "next-auth";
import { useSession } from "next-auth/react";
import { usePathname } from "next/navigation";
import { FC, PropsWithChildren } from "react";
import { useLazyLoadQuery } from "react-relay";
import { graphql } from "relay-runtime";

import { RootLayoutQuery } from "@/__generated__/RootLayoutQuery.graphql";
import { isModelRoute, isModelSubroute } from "@/routes";
import { PageFooter } from "../components/layout/RootLayout/PageFooter";
import { PageMenu } from "../components/layout/RootLayout/PageMenu";
import { ClientApp } from "./ClientApp";

const InnerRootLayout: FC<PropsWithChildren> = ({ children }) => {
const { data: session } = useSession();
const queryData = useLazyLoadQuery<RootLayoutQuery>(
graphql`
query RootLayoutQuery($signedIn: Boolean!) {
...PageMenu @arguments(signedIn: $signedIn)
}
`,
{ signedIn: !!session }
);

const pathname = usePathname();

const showFooter = !isModelRoute(pathname);
const backgroundColor = isModelSubroute(pathname) ? "bg-white" : "bg-gray-50";

return (
<div className={clsx("min-h-screen flex flex-col", backgroundColor)}>
<PageMenu queryRef={queryData} />
<div
// This allows us to center children vertically if necessary, e.g. in `not-found.tsx`.
// Note that setting `height: 100%` instead of `flex-1` on children won't work;
// see https://stackoverflow.com/questions/8468066/child-inside-parent-with-min-height-100-not-inheriting-height for details.
className="flex-1 flex flex-col"
>
{children}
</div>
{showFooter && <PageFooter />}
</div>
);
};

export const RootLayout: FC<
PropsWithChildren<{
session: Session | null;
}>
> = ({ session, children }) => {
return (
<ClientApp session={session}>
<InnerRootLayout>{children}</InnerRootLayout>
</ClientApp>
);
};
57 changes: 45 additions & 12 deletions packages/hub/src/app/groups/[slug]/GroupLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
"use client";
import { GroupIcon } from "@quri/ui";
import { FC, PropsWithChildren } from "react";
import { useSubscribeToInvalidationState } from "react-relay";
import { graphql } from "relay-runtime";

import { Button, GroupIcon, PlusIcon } from "@quri/ui";

import { GroupLayoutQuery } from "@/__generated__/GroupLayoutQuery.graphql";
import { H1 } from "@/components/ui/Headers";
import { StyledTabLink } from "@/components/ui/StyledTabLink";
import { extractFromGraphqlErrorUnion } from "@/lib/graphqlHelpers";
import { SerializablePreloadedQuery } from "@/relay/loadPageQuery";
import { usePageQuery } from "@/relay/usePageQuery";
import { groupMembersRoute, groupRoute } from "@/routes";
import { FC, PropsWithChildren } from "react";
import { graphql } from "relay-runtime";
import { groupMembersRoute, groupRoute, newModelRoute } from "@/routes";
import { InviteForMe } from "./InviteForMe";
import { useSubscribeToInvalidationState } from "react-relay";
import { useIsGroupMember } from "./hooks";
import { useRouter, useSelectedLayoutSegment } from "next/navigation";

const Query = graphql`
query GroupLayoutQuery($slug: String!) {
Expand All @@ -27,12 +30,34 @@ const Query = graphql`
id
slug
...hooks_useIsGroupAdmin
...hooks_useIsGroupMember
...InviteForMe
}
}
}
`;

const NewButton: FC<{ group: string }> = ({ group }) => {
const segment = useSelectedLayoutSegment();

let link = newModelRoute({ group });

const router = useRouter();

if (segment === "members") {
return null;
}

return (
<Button onClick={() => router.push(link)}>
<div className="flex gap-1 items-center">
<PlusIcon size={16} />
New Model
</div>
</Button>
);
};

export const GroupLayout: FC<
PropsWithChildren<{
query: SerializablePreloadedQuery<GroupLayoutQuery>;
Expand All @@ -43,6 +68,8 @@ export const GroupLayout: FC<

useSubscribeToInvalidationState([group.id], reload);

const isMember = useIsGroupMember(group);

return (
<div className="space-y-8">
<H1 size="large">
Expand All @@ -52,13 +79,19 @@ export const GroupLayout: FC<
</div>
</H1>
<InviteForMe groupRef={group} />
<StyledTabLink.List>
<StyledTabLink name="Models" href={groupRoute({ slug: group.slug })} />
<StyledTabLink
name="Members"
href={groupMembersRoute({ slug: group.slug })}
/>
</StyledTabLink.List>
<div className="flex items-center gap-2">
<StyledTabLink.List>
<StyledTabLink
name="Models"
href={groupRoute({ slug: group.slug })}
/>
<StyledTabLink
name="Members"
href={groupMembersRoute({ slug: group.slug })}
/>
</StyledTabLink.List>
{isMember && <NewButton group={group.slug} />}
</div>
<div>{children}</div>
</div>
);
Expand Down
14 changes: 14 additions & 0 deletions packages/hub/src/app/groups/[slug]/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ export function useIsGroupAdmin(groupRef: hooks_useIsGroupAdmin$key) {
);
return myMembership?.role === "Admin";
}

export function useIsGroupMember(groupRef: hooks_useIsGroupAdmin$key) {
const { myMembership } = useFragment(
graphql`
fragment hooks_useIsGroupMember on Group {
myMembership {
id
}
}
`,
groupRef
);
return Boolean(myMembership);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { FC } from "react";
import { FaPlus } from "react-icons/fa";
import { useFragment } from "react-relay";
import { ConnectionHandler, graphql } from "relay-runtime";

import { PlusIcon, SelectStringFormField } from "@quri/ui";

import { InviteUserToGroupActionMutation } from "@/__generated__/InviteUserToGroupActionMutation.graphql";
import { InviteUserToGroupAction_group$key } from "@/__generated__/InviteUserToGroupAction_group.graphql";
import { MembershipRole } from "@/__generated__/SetMembershipRoleActionMutation.graphql";
import { SelectUser, SelectUserOption } from "@/components/SelectUser";
import { MutationModalAction } from "@/components/ui/MutationModalAction";
import { SelectStringFormField } from "@quri/ui";

const Mutation = graphql`
mutation InviteUserToGroupActionMutation(
Expand Down Expand Up @@ -54,7 +54,7 @@ export const InviteUserToGroupAction: FC<Props> = ({ groupRef, close }) => {
return (
<MutationModalAction<InviteUserToGroupActionMutation, FormShape>
title="Invite"
icon={FaPlus}
icon={PlusIcon}
close={close}
mutation={Mutation}
expectedTypename="InviteUserToGroupResult"
Expand Down
11 changes: 5 additions & 6 deletions packages/hub/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,18 @@ import "react-loading-skeleton/dist/skeleton.css";

import "@/styles/main.css";

import { ClientApp } from "./ClientApp";
import { RootLayout } from "./RootLayout";
import { authOptions } from "./api/auth/[...nextauth]/authOptions";
import { ErrorBoundary } from "@/components/ErrorBoundary";

export default async function RootLayout({ children }: PropsWithChildren) {
export default async function ServerRootLayout({
children,
}: PropsWithChildren) {
const session = await getServerSession(authOptions);

return (
<html>
<body>
<ClientApp session={session}>
<ErrorBoundary>{children}</ErrorBoundary>
</ClientApp>
<RootLayout session={session}>{children}</RootLayout>
</body>
</html>
);
Expand Down
1 change: 0 additions & 1 deletion packages/hub/src/app/models/[owner]/[slug]/ModelLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export const ModelLayout: FC<
const dropDown = (close: () => void) => (
<DropdownMenu>
<DropdownMenuHeader>Relative Value Functions</DropdownMenuHeader>
<DropdownMenuSeparator />
{model.currentRevision.relativeValuesExports.map((exportItem) => (
<DropdownMenuNextLinkItem
key={exportItem.variableName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ export const MoveModelAction: FC<Props> = ({ model: modelKey, close }) => {
}}
submitText="Save"
close={close}
title="Move"
title="Change Owner"
icon={RightArrowIcon}
modalTitle={`Move model ${model.owner.slug}/${model.slug}`}
modalTitle={`Change owner for ${model.owner.slug}/${model.slug}`}
onCompleted={({ model }) => {
router.push(modelRoute({ owner: model.owner.slug, slug: model.slug }));
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export const CacheMenu: FC<{
definition.items.length * definition.items.length
} pairs cached`}
</DropdownMenuHeader>
<DropdownMenuSeparator />
{!fullyCached && (
<BuildRelativeValuesCacheAction
exportId={revision.forRelativeValues.id}
Expand Down
Loading

4 comments on commit 7f70aa7

@vercel
Copy link

@vercel vercel bot commented on 7f70aa7 Sep 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 7f70aa7 Sep 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 7f70aa7 Sep 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

quri-ui – ./packages/ui

quri-ui-quantified-uncertainty.vercel.app
quri-ui.vercel.app
quri-ui-git-main-quantified-uncertainty.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 7f70aa7 Sep 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.