From 4a5ec4b7cea956c1e6510b24f3719df2db4246d7 Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Sat, 29 Jun 2024 07:53:18 +0000 Subject: [PATCH 1/5] feat: add project settings tab --- webui/graphql.schema.json | 109 ++++++++++++++++++ webui/src/Components/Header/Header.tsx | 21 +++- .../src/Components/Header/HeaderWithData.tsx | 9 +- webui/src/Components/Header/styles.tsx | 7 ++ .../Project/MainContent/MainContent.tsx | 13 +++ .../Project/MainContent/Settings/Settings.tsx | 105 +++++++++++++++++ .../MainContent/Settings/SettingsWithData.tsx | 53 +++++++++ .../Project/MainContent/Settings/index.tsx | 3 + .../Project/MainContent/Settings/schema.tsx | 7 ++ .../Project/MainContent/Settings/styles.tsx | 68 +++++++++++ webui/src/Containers/Project/ProjectState.tsx | 11 ++ webui/src/GraphQL/Fragment.tsx | 3 + webui/src/GraphQL/Project/Mutation.tsx | 19 +++ webui/src/Hooks/GraphQL.tsx | 69 ++++++++++- 14 files changed, 489 insertions(+), 8 deletions(-) create mode 100644 webui/src/Containers/Project/MainContent/Settings/Settings.tsx create mode 100644 webui/src/Containers/Project/MainContent/Settings/SettingsWithData.tsx create mode 100644 webui/src/Containers/Project/MainContent/Settings/index.tsx create mode 100644 webui/src/Containers/Project/MainContent/Settings/schema.tsx create mode 100644 webui/src/Containers/Project/MainContent/Settings/styles.tsx create mode 100644 webui/src/Containers/Project/ProjectState.tsx diff --git a/webui/graphql.schema.json b/webui/graphql.schema.json index 08742a9..06436c1 100644 --- a/webui/graphql.schema.json +++ b/webui/graphql.schema.json @@ -958,6 +958,71 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "updateProject", + "description": null, + "args": [ + { + "name": "description", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "tags", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Project", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -1344,6 +1409,30 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "displayName", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "id", "description": null, @@ -1459,6 +1548,26 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "tags", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, diff --git a/webui/src/Components/Header/Header.tsx b/webui/src/Components/Header/Header.tsx index c355c0f..3bf58b6 100644 --- a/webui/src/Components/Header/Header.tsx +++ b/webui/src/Components/Header/Header.tsx @@ -1,10 +1,18 @@ import { FC } from "react"; +import { Github } from "@styled-icons/bootstrap"; import { EllipsisVertical } from "@styled-icons/fa-solid"; import { StatefulPopover } from "baseui/popover"; import { StatefulMenu } from "baseui/menu"; import { Breadcrumbs } from "baseui/breadcrumbs"; -import styles, { Link, Container, RunButton, PopoverButton } from "./styles"; +import styles, { + Link, + Container, + RunButton, + PopoverButton, + GithubLink, +} from "./styles"; import { Spinner } from "baseui/spinner"; +import { Repository } from "../../Hooks/GraphQL"; export type HeaderProps = { id: string; @@ -13,10 +21,12 @@ export type HeaderProps = { breadcrumbs?: { title: string; link?: string }[]; showRunButton?: boolean; loading?: boolean; + linkedRepository?: Repository | null; }; const Header: FC = (props) => { - const { id, breadcrumbs, onRun, menu, showRunButton } = props; + const { id, breadcrumbs, onRun, menu, showRunButton, linkedRepository } = + props; return ( = (props) => { return {title}; })} + {linkedRepository && ( +
+ + + +
+ )} {!!menu?.length && ( = () => { const navigate = useNavigate(); const { pathname } = useLocation(); const { id } = useParams(); - const [project, setProject] = useState(null); + const [{ project }, setProjectState] = useRecoilState(ProjectState); // eslint-disable-next-line @typescript-eslint/no-explicit-any const [actions, setActions] = useState([]); const [run, setRun] = useState(null); @@ -55,6 +56,9 @@ const HeaderWithData: FC = () => { const [getLinkedRepository] = useGetLinkedRepositoryLazyQuery(); const [loading, setLoading] = useState(false); + const setProject = (value?: Project | null) => + setProjectState({ project: value }); + useEffect(() => { if (pathname.startsWith("/run")) { getRun().then(({ data }) => { @@ -166,6 +170,7 @@ const HeaderWithData: FC = () => { (!!linkedRepository && !!actions?.filter((x) => x.enabled).length)) } loading={loading} + linkedRepository={linkedRepository} /> ); }; diff --git a/webui/src/Components/Header/styles.tsx b/webui/src/Components/Header/styles.tsx index d58e219..2dc9dc0 100644 --- a/webui/src/Components/Header/styles.tsx +++ b/webui/src/Components/Header/styles.tsx @@ -50,6 +50,13 @@ export const PopoverButton = styled.button` margin-right: 10px; `; +export const GithubLink = styled.a` + color: #fff; + &:hover { + color: #24ffb5; + } +`; + export default { Tab: { Tab: { diff --git a/webui/src/Containers/Project/MainContent/MainContent.tsx b/webui/src/Containers/Project/MainContent/MainContent.tsx index d596337..668702a 100644 --- a/webui/src/Containers/Project/MainContent/MainContent.tsx +++ b/webui/src/Containers/Project/MainContent/MainContent.tsx @@ -2,10 +2,12 @@ import { FC, useState } from "react"; import { Tabs, Tab } from "baseui/tabs-motion"; import { CollectionPlay } from "@styled-icons/bootstrap"; import { SettingsOutline } from "@styled-icons/evaicons-outline"; +import { Options } from "@styled-icons/fluentui-system-regular"; import Runs from "./Runs"; import Composer from "./Composer"; import styles, { Container } from "./styles"; import Header from "../../../Components/Header"; +import Settings from "./Settings"; export type MainContentProps = { onTabChange: (activeKey: string) => void; @@ -48,6 +50,17 @@ const MainContent: FC = ({ onTabChange }) => { > + + + Settings + + } + overrides={styles.Tab} + > + +
); diff --git a/webui/src/Containers/Project/MainContent/Settings/Settings.tsx b/webui/src/Containers/Project/MainContent/Settings/Settings.tsx new file mode 100644 index 0000000..5421543 --- /dev/null +++ b/webui/src/Containers/Project/MainContent/Settings/Settings.tsx @@ -0,0 +1,105 @@ +import { BaseSyntheticEvent, FC } from "react"; +import styles, { + Title, + Text, + Label, + Section, + Cluster, + ClusterStart, + SaveButton, +} from "./styles"; +import { Input } from "baseui/input"; +import { Controller, useFormContext } from "react-hook-form"; +import { Account } from "../../../../Hooks/GraphQL"; +import { Spinner } from "baseui/spinner"; + +export type SettingsProps = { + loading: boolean; + me?: Account | null; + handleSubmit: (e: BaseSyntheticEvent) => void; +}; + +const Settings: FC = ({ handleSubmit, me, loading }) => { + const { control, watch } = useFormContext(); + const name = watch("name"); + return ( + <> +
+ General + + Here you can change the name and description of your project + (pipeline), or even decide to share it with the rest of the world. + +
+ {!!me && ( +
+ + + FL-10 +
+ 6 vCPU · 16GB memory +
+
+
+ )} +
+ + ( + + )} + /> +
+
+ + } + /> +
+
+ + } + /> + + Short words to categorize this pipeline. Separate multiple tags with a + comma + +
+ {!loading && Save} + {loading && ( + + + + )} + + ); +}; + +export default Settings; diff --git a/webui/src/Containers/Project/MainContent/Settings/SettingsWithData.tsx b/webui/src/Containers/Project/MainContent/Settings/SettingsWithData.tsx new file mode 100644 index 0000000..2debe67 --- /dev/null +++ b/webui/src/Containers/Project/MainContent/Settings/SettingsWithData.tsx @@ -0,0 +1,53 @@ +import { FC, useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import Settings from "./Settings"; +import { schema } from "./schema"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useRecoilValue } from "recoil"; +import { ProjectState } from "../../ProjectState"; +import { AuthState } from "../../../Auth/AuthState"; +import { useUpdateProjectMutation } from "../../../../Hooks/GraphQL"; +import _ from "lodash"; + +const SettingsWithData: FC = () => { + const [updateProject] = useUpdateProjectMutation(); + const [loading, setLoading] = useState(false); + const me = useRecoilValue(AuthState); + const { project } = useRecoilValue(ProjectState); + const methods = useForm({ + mode: "onChange", + resolver: zodResolver(schema), + defaultValues: { + name: project?.name, + description: project?.description, + tags: _.get(project, "tags", [])?.join(", "), + }, + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const onSubmit = (data: any) => { + setLoading(true); + updateProject({ + variables: { + id: project!.id, + name: data.name, + description: data.description, + tags: data.tags, + }, + }) + .then(() => setLoading(false)) + .catch(() => setLoading(false)); + }; + + return ( + + + + ); +}; + +export default SettingsWithData; diff --git a/webui/src/Containers/Project/MainContent/Settings/index.tsx b/webui/src/Containers/Project/MainContent/Settings/index.tsx new file mode 100644 index 0000000..5819556 --- /dev/null +++ b/webui/src/Containers/Project/MainContent/Settings/index.tsx @@ -0,0 +1,3 @@ +import Settings from "./SettingsWithData"; + +export default Settings; diff --git a/webui/src/Containers/Project/MainContent/Settings/schema.tsx b/webui/src/Containers/Project/MainContent/Settings/schema.tsx new file mode 100644 index 0000000..0e13f98 --- /dev/null +++ b/webui/src/Containers/Project/MainContent/Settings/schema.tsx @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const schema = z.object({ + name: z.string().min(1, { message: "Please enter a name" }), + description: z.string().optional().nullable(), + tags: z.string().optional().nullable(), +}); diff --git a/webui/src/Containers/Project/MainContent/Settings/styles.tsx b/webui/src/Containers/Project/MainContent/Settings/styles.tsx new file mode 100644 index 0000000..0dbc2e0 --- /dev/null +++ b/webui/src/Containers/Project/MainContent/Settings/styles.tsx @@ -0,0 +1,68 @@ +import styled from "@emotion/styled"; + +export const Title = styled.div` + font-size: 18px; + margin-bottom: 4px; +`; + +export const Text = styled.p` + font-size: 14px; + margin: 0px; + color: #ffffffb2; +`; + +export const Label = styled.div` + font-size: 14px; + font-weight: 600; + margin-bottom: 8px; +`; + +export const Section = styled.div` + margin-bottom: 32px; +`; + +export const ClusterStart = styled.div` + color: #f701ad; + border-right: 1px solid #f701ad; + padding: 6px; + background-color: #f701ad28; +`; + +export const Cluster = styled.div` + font-size: 13px; + border: 1px solid #f701ad; + width: 230px; + border-radius: 6px; + display: flex; + align-items: center; +`; + +export const SaveButton = styled.button` + display: flex; + justify-content: center; + align-items: center; + height: 40px; + background-color: #24ffb5; + color: #000; + border: none; + font-weight: 600; + width: 150px; + cursor: pointer; + font-family: Lexend; + &:hover { + opacity: 0.8; + } +`; + +export default { + Input: { + Root: { + style: { border: "1px solid #ffffff28" }, + }, + Input: { + style: { + fontFamily: "Lexend", + }, + }, + }, +}; diff --git a/webui/src/Containers/Project/ProjectState.tsx b/webui/src/Containers/Project/ProjectState.tsx new file mode 100644 index 0000000..512d68a --- /dev/null +++ b/webui/src/Containers/Project/ProjectState.tsx @@ -0,0 +1,11 @@ +import { atom } from "recoil"; +import { Project } from "../../Hooks/GraphQL"; + +export const ProjectState = atom<{ + project?: Project | null; +}>({ + key: "project-state", + default: { + project: null, + }, +}); diff --git a/webui/src/GraphQL/Fragment.tsx b/webui/src/GraphQL/Fragment.tsx index d2fdaed..6c20bce 100644 --- a/webui/src/GraphQL/Fragment.tsx +++ b/webui/src/GraphQL/Fragment.tsx @@ -28,6 +28,9 @@ export const ProjectFragment = gql` fragment ProjectFragment on Project { id name + displayName + description + tags path createdAt picture diff --git a/webui/src/GraphQL/Project/Mutation.tsx b/webui/src/GraphQL/Project/Mutation.tsx index ad1df79..bc197d4 100644 --- a/webui/src/GraphQL/Project/Mutation.tsx +++ b/webui/src/GraphQL/Project/Mutation.tsx @@ -9,3 +9,22 @@ export const CREATE_PROJECT = gql` } ${ProjectFragment} `; + +export const UPDATE_PROJECT = gql` + mutation UpdateProject( + $id: ID! + $name: String + $description: String + $tags: String + ) { + updateProject( + id: $id + name: $name + description: $description + tags: $tags + ) { + ...ProjectFragment + } + } + ${ProjectFragment} +`; diff --git a/webui/src/Hooks/GraphQL.tsx b/webui/src/Hooks/GraphQL.tsx index bdebf71..c70d9df 100644 --- a/webui/src/Hooks/GraphQL.tsx +++ b/webui/src/Hooks/GraphQL.tsx @@ -86,6 +86,7 @@ export type Mutation = { runPipeline?: Maybe; saveActions?: Maybe>; unlinkRepository?: Maybe; + updateProject?: Maybe; }; @@ -132,6 +133,14 @@ export type MutationUnlinkRepositoryArgs = { repoName: Scalars['String']; }; + +export type MutationUpdateProjectArgs = { + description?: InputMaybe; + id: Scalars['ID']; + name?: InputMaybe; + tags?: InputMaybe; +}; + export type Organization = { __typename?: 'Organization'; createdAt: Scalars['String']; @@ -168,6 +177,8 @@ export type Project = { buildsPerWeek?: Maybe; createdAt: Scalars['String']; cursor?: Maybe; + description?: Maybe; + displayName?: Maybe; id: Scalars['ID']; logs?: Maybe; name: Scalars['String']; @@ -176,6 +187,7 @@ export type Project = { recentRuns?: Maybe>; reliability?: Maybe; speed?: Maybe; + tags?: Maybe>; }; export type Query = { @@ -385,7 +397,7 @@ export type ExportActionsQuery = { __typename?: 'Query', exportActions: string } export type RunFragmentFragment = { __typename?: 'Run', id: string, branch?: string | null, commit?: string | null, date: string, project: string, projectId: string, duration?: number | null, message?: string | null, name: string, title: string, cursor?: string | null, status?: string | null, jobs: Array<{ __typename?: 'Job', id: string, name: string, createdAt: string, status: string, duration?: number | null }> }; -export type ProjectFragmentFragment = { __typename?: 'Project', id: string, name: string, path?: string | null, createdAt: string, picture: string, speed?: number | null, reliability?: number | null, buildsPerWeek?: number | null, recentRuns?: Array<{ __typename?: 'Run', id: string, branch?: string | null, commit?: string | null, date: string, project: string, projectId: string, duration?: number | null, message?: string | null, name: string, title: string, cursor?: string | null, status?: string | null, jobs: Array<{ __typename?: 'Job', id: string, name: string, createdAt: string, status: string, duration?: number | null }> }> | null }; +export type ProjectFragmentFragment = { __typename?: 'Project', id: string, name: string, displayName?: string | null, description?: string | null, tags?: Array | null, path?: string | null, createdAt: string, picture: string, speed?: number | null, reliability?: number | null, buildsPerWeek?: number | null, recentRuns?: Array<{ __typename?: 'Run', id: string, branch?: string | null, commit?: string | null, date: string, project: string, projectId: string, duration?: number | null, message?: string | null, name: string, title: string, cursor?: string | null, status?: string | null, jobs: Array<{ __typename?: 'Job', id: string, name: string, createdAt: string, status: string, duration?: number | null }> }> | null }; export type LogFragmentFragment = { __typename?: 'Log', id: string, message: string, createdAt: string }; @@ -444,7 +456,17 @@ export type GetOrganizationsQuery = { __typename?: 'Query', organizations: Array export type CreateProjectMutationVariables = Exact<{ [key: string]: never; }>; -export type CreateProjectMutation = { __typename?: 'Mutation', createProject: { __typename?: 'Project', id: string, name: string, path?: string | null, createdAt: string, picture: string, speed?: number | null, reliability?: number | null, buildsPerWeek?: number | null, recentRuns?: Array<{ __typename?: 'Run', id: string, branch?: string | null, commit?: string | null, date: string, project: string, projectId: string, duration?: number | null, message?: string | null, name: string, title: string, cursor?: string | null, status?: string | null, jobs: Array<{ __typename?: 'Job', id: string, name: string, createdAt: string, status: string, duration?: number | null }> }> | null } }; +export type CreateProjectMutation = { __typename?: 'Mutation', createProject: { __typename?: 'Project', id: string, name: string, displayName?: string | null, description?: string | null, tags?: Array | null, path?: string | null, createdAt: string, picture: string, speed?: number | null, reliability?: number | null, buildsPerWeek?: number | null, recentRuns?: Array<{ __typename?: 'Run', id: string, branch?: string | null, commit?: string | null, date: string, project: string, projectId: string, duration?: number | null, message?: string | null, name: string, title: string, cursor?: string | null, status?: string | null, jobs: Array<{ __typename?: 'Job', id: string, name: string, createdAt: string, status: string, duration?: number | null }> }> | null } }; + +export type UpdateProjectMutationVariables = Exact<{ + id: Scalars['ID']; + name?: InputMaybe; + description?: InputMaybe; + tags?: InputMaybe; +}>; + + +export type UpdateProjectMutation = { __typename?: 'Mutation', updateProject?: { __typename?: 'Project', id: string, name: string, displayName?: string | null, description?: string | null, tags?: Array | null, path?: string | null, createdAt: string, picture: string, speed?: number | null, reliability?: number | null, buildsPerWeek?: number | null, recentRuns?: Array<{ __typename?: 'Run', id: string, branch?: string | null, commit?: string | null, date: string, project: string, projectId: string, duration?: number | null, message?: string | null, name: string, title: string, cursor?: string | null, status?: string | null, jobs: Array<{ __typename?: 'Job', id: string, name: string, createdAt: string, status: string, duration?: number | null }> }> | null } | null }; export type GetProjectsQueryVariables = Exact<{ cursor?: InputMaybe; @@ -454,14 +476,14 @@ export type GetProjectsQueryVariables = Exact<{ }>; -export type GetProjectsQuery = { __typename?: 'Query', projects: Array<{ __typename?: 'Project', id: string, name: string, path?: string | null, createdAt: string, picture: string, speed?: number | null, reliability?: number | null, buildsPerWeek?: number | null, recentRuns?: Array<{ __typename?: 'Run', id: string, branch?: string | null, commit?: string | null, date: string, project: string, projectId: string, duration?: number | null, message?: string | null, name: string, title: string, cursor?: string | null, status?: string | null, jobs: Array<{ __typename?: 'Job', id: string, name: string, createdAt: string, status: string, duration?: number | null }> }> | null }> }; +export type GetProjectsQuery = { __typename?: 'Query', projects: Array<{ __typename?: 'Project', id: string, name: string, displayName?: string | null, description?: string | null, tags?: Array | null, path?: string | null, createdAt: string, picture: string, speed?: number | null, reliability?: number | null, buildsPerWeek?: number | null, recentRuns?: Array<{ __typename?: 'Run', id: string, branch?: string | null, commit?: string | null, date: string, project: string, projectId: string, duration?: number | null, message?: string | null, name: string, title: string, cursor?: string | null, status?: string | null, jobs: Array<{ __typename?: 'Job', id: string, name: string, createdAt: string, status: string, duration?: number | null }> }> | null }> }; export type GetProjectQueryVariables = Exact<{ id: Scalars['ID']; }>; -export type GetProjectQuery = { __typename?: 'Query', project?: { __typename?: 'Project', id: string, name: string, path?: string | null, createdAt: string, picture: string, speed?: number | null, reliability?: number | null, buildsPerWeek?: number | null, recentRuns?: Array<{ __typename?: 'Run', id: string, branch?: string | null, commit?: string | null, date: string, project: string, projectId: string, duration?: number | null, message?: string | null, name: string, title: string, cursor?: string | null, status?: string | null, jobs: Array<{ __typename?: 'Job', id: string, name: string, createdAt: string, status: string, duration?: number | null }> }> | null } | null }; +export type GetProjectQuery = { __typename?: 'Query', project?: { __typename?: 'Project', id: string, name: string, displayName?: string | null, description?: string | null, tags?: Array | null, path?: string | null, createdAt: string, picture: string, speed?: number | null, reliability?: number | null, buildsPerWeek?: number | null, recentRuns?: Array<{ __typename?: 'Run', id: string, branch?: string | null, commit?: string | null, date: string, project: string, projectId: string, duration?: number | null, message?: string | null, name: string, title: string, cursor?: string | null, status?: string | null, jobs: Array<{ __typename?: 'Job', id: string, name: string, createdAt: string, status: string, duration?: number | null }> }> | null } | null }; export type CountProjectsQueryVariables = Exact<{ [key: string]: never; }>; @@ -556,6 +578,9 @@ export const ProjectFragmentFragmentDoc = gql` fragment ProjectFragment on Project { id name + displayName + description + tags path createdAt picture @@ -1113,6 +1138,42 @@ export function useCreateProjectMutation(baseOptions?: Apollo.MutationHookOption export type CreateProjectMutationHookResult = ReturnType; export type CreateProjectMutationResult = Apollo.MutationResult; export type CreateProjectMutationOptions = Apollo.BaseMutationOptions; +export const UpdateProjectDocument = gql` + mutation UpdateProject($id: ID!, $name: String, $description: String, $tags: String) { + updateProject(id: $id, name: $name, description: $description, tags: $tags) { + ...ProjectFragment + } +} + ${ProjectFragmentFragmentDoc}`; +export type UpdateProjectMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateProjectMutation__ + * + * To run a mutation, you first call `useUpdateProjectMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateProjectMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updateProjectMutation, { data, loading, error }] = useUpdateProjectMutation({ + * variables: { + * id: // value for 'id' + * name: // value for 'name' + * description: // value for 'description' + * tags: // value for 'tags' + * }, + * }); + */ +export function useUpdateProjectMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateProjectDocument, options); + } +export type UpdateProjectMutationHookResult = ReturnType; +export type UpdateProjectMutationResult = Apollo.MutationResult; +export type UpdateProjectMutationOptions = Apollo.BaseMutationOptions; export const GetProjectsDocument = gql` query GetProjects($cursor: String, $limit: Int, $skip: Int, $reverse: Boolean) { projects(cursor: $cursor, limit: $limit, skip: $skip, reverse: $reverse) { From 148727d37ccdc3706a3885a21c1bd326d8e026a0 Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Sat, 29 Jun 2024 20:23:44 +0000 Subject: [PATCH 2/5] show project 'displayName' if available --- webui/src/Components/Header/HeaderWithData.tsx | 2 +- webui/src/Containers/Home/MainContent/MainContent.tsx | 2 +- .../Project/MainContent/Settings/SettingsWithData.tsx | 11 +++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/webui/src/Components/Header/HeaderWithData.tsx b/webui/src/Components/Header/HeaderWithData.tsx index a6b8415..af8edcf 100644 --- a/webui/src/Components/Header/HeaderWithData.tsx +++ b/webui/src/Components/Header/HeaderWithData.tsx @@ -128,7 +128,7 @@ const HeaderWithData: FC = () => { link: "/", }, { - title: project?.name || "", + title: project?.displayName || project?.name || "", link: run && project?.id ? `/project/${project.id}` : undefined, }, ]; diff --git a/webui/src/Containers/Home/MainContent/MainContent.tsx b/webui/src/Containers/Home/MainContent/MainContent.tsx index 7960b9b..3ff86f8 100644 --- a/webui/src/Containers/Home/MainContent/MainContent.tsx +++ b/webui/src/Containers/Home/MainContent/MainContent.tsx @@ -39,7 +39,7 @@ const MainContent: FC = (props) => {
-
{item.name}
+
{item.displayName || item.name}
{item.path !== "empty" ? item.path : ""}
{_.get(item, "recentRuns.0.status") && ( diff --git a/webui/src/Containers/Project/MainContent/Settings/SettingsWithData.tsx b/webui/src/Containers/Project/MainContent/Settings/SettingsWithData.tsx index 2debe67..b02da1f 100644 --- a/webui/src/Containers/Project/MainContent/Settings/SettingsWithData.tsx +++ b/webui/src/Containers/Project/MainContent/Settings/SettingsWithData.tsx @@ -3,7 +3,7 @@ import { FormProvider, useForm } from "react-hook-form"; import Settings from "./Settings"; import { schema } from "./schema"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useRecoilValue } from "recoil"; +import { useRecoilState, useRecoilValue } from "recoil"; import { ProjectState } from "../../ProjectState"; import { AuthState } from "../../../Auth/AuthState"; import { useUpdateProjectMutation } from "../../../../Hooks/GraphQL"; @@ -13,12 +13,12 @@ const SettingsWithData: FC = () => { const [updateProject] = useUpdateProjectMutation(); const [loading, setLoading] = useState(false); const me = useRecoilValue(AuthState); - const { project } = useRecoilValue(ProjectState); + const [{ project }, setProject] = useRecoilState(ProjectState); const methods = useForm({ mode: "onChange", resolver: zodResolver(schema), defaultValues: { - name: project?.name, + name: project?.displayName || project?.name, description: project?.description, tags: _.get(project, "tags", [])?.join(", "), }, @@ -35,7 +35,10 @@ const SettingsWithData: FC = () => { tags: data.tags, }, }) - .then(() => setLoading(false)) + .then(({ data }) => { + setProject({ project: data?.updateProject }); + setLoading(false); + }) .catch(() => setLoading(false)); }; From a88893860d04f2d1d60472d3132cbdbde9d6bde7 Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Sat, 29 Jun 2024 22:52:55 +0000 Subject: [PATCH 3/5] display project tags and description --- webui/src/Components/Header/Header.tsx | 87 +++++++++++++------ .../src/Components/Header/HeaderWithData.tsx | 13 +-- webui/src/Components/Header/styles.tsx | 2 +- .../Home/MainContent/MainContent.tsx | 24 ++++- .../Containers/Home/MainContent/styles.tsx | 1 + 5 files changed, 91 insertions(+), 36 deletions(-) diff --git a/webui/src/Components/Header/Header.tsx b/webui/src/Components/Header/Header.tsx index 3bf58b6..b8a11ff 100644 --- a/webui/src/Components/Header/Header.tsx +++ b/webui/src/Components/Header/Header.tsx @@ -12,7 +12,7 @@ import styles, { GithubLink, } from "./styles"; import { Spinner } from "baseui/spinner"; -import { Repository } from "../../Hooks/GraphQL"; +import { Project, Repository } from "../../Hooks/GraphQL"; export type HeaderProps = { id: string; @@ -22,6 +22,7 @@ export type HeaderProps = { showRunButton?: boolean; loading?: boolean; linkedRepository?: Repository | null; + project?: Project | null; }; const Header: FC = (props) => { @@ -29,34 +30,66 @@ const Header: FC = (props) => { props; return ( - ({ - fontFamily: $theme.primaryFontFamily, - }), - }, +
+ +
+
- {breadcrumbs?.map(({ title, link }, index) => { - if (link) { - return ( - - {title} - - ); - } - return {title}; - })} - + ({ + fontFamily: $theme.primaryFontFamily, + }), + }, + }} + > + {breadcrumbs?.map(({ title, link }, index) => { + if (link) { + return ( + + {title} + + ); + } + return {title}; + })} + +

+ {props.project?.description} +

+
{linkedRepository && (
diff --git a/webui/src/Components/Header/HeaderWithData.tsx b/webui/src/Components/Header/HeaderWithData.tsx index af8edcf..21e1ae9 100644 --- a/webui/src/Components/Header/HeaderWithData.tsx +++ b/webui/src/Components/Header/HeaderWithData.tsx @@ -46,7 +46,7 @@ const HeaderWithData: FC = () => { const [getActions] = useGetActionsLazyQuery(); const { data } = useGetActionsQuery({ variables: { - projectId: project?.id || "", + projectId: id!, }, fetchPolicy: "cache-and-network", }); @@ -90,16 +90,16 @@ const HeaderWithData: FC = () => { }, [pathname]); useEffect(() => { - if (!me || !project) { + if (!me || !id) { return; } getLinkedRepository({ variables: { - projectId: project.id, + projectId: pathname.startsWith("/run") ? project!.id : id, }, - fetchPolicy: "cache-and-network", + fetchPolicy: "network-only", }).then((res) => setLinkedRepository(res.data?.linkedRepository)); - }, [me, getLinkedRepository, project]); + }, [me, getLinkedRepository, id, project, pathname]); useEffect(() => { if (!data) { @@ -146,7 +146,7 @@ const HeaderWithData: FC = () => { setTimeout(() => { getActions({ variables: { - projectId: project.id, + projectId: pathname.startsWith("/run") ? project!.id : id!, }, fetchPolicy: "cache-and-network", }).then(({ data }) => { @@ -171,6 +171,7 @@ const HeaderWithData: FC = () => { } loading={loading} linkedRepository={linkedRepository} + project={project} /> ); }; diff --git a/webui/src/Components/Header/styles.tsx b/webui/src/Components/Header/styles.tsx index 2dc9dc0..0e79c07 100644 --- a/webui/src/Components/Header/styles.tsx +++ b/webui/src/Components/Header/styles.tsx @@ -19,8 +19,8 @@ export const Container = styled.div` display: flex; flex-direction: row; justify-content: space-between; - margin-bottom: 20px; height: 40px; + margin-bottom: 20px; `; export const RunButton = styled.button` diff --git a/webui/src/Containers/Home/MainContent/MainContent.tsx b/webui/src/Containers/Home/MainContent/MainContent.tsx index 3ff86f8..9ea976a 100644 --- a/webui/src/Containers/Home/MainContent/MainContent.tsx +++ b/webui/src/Containers/Home/MainContent/MainContent.tsx @@ -32,7 +32,7 @@ const MainContent: FC = (props) => { @@ -40,7 +40,27 @@ const MainContent: FC = (props) => {
{item.displayName || item.name}
- {item.path !== "empty" ? item.path : ""} + {item.path !== "empty" && !!item.path && ( + {item.path} + )} +
+ {item.tags?.map((tag) => ( + + {tag} + + ))} +
{_.get(item, "recentRuns.0.status") && ( Date: Sat, 29 Jun 2024 23:02:26 +0000 Subject: [PATCH 4/5] disable run button if project is not configured --- webui/src/Components/Header/HeaderWithData.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webui/src/Components/Header/HeaderWithData.tsx b/webui/src/Components/Header/HeaderWithData.tsx index 21e1ae9..65e279d 100644 --- a/webui/src/Components/Header/HeaderWithData.tsx +++ b/webui/src/Components/Header/HeaderWithData.tsx @@ -165,7 +165,8 @@ const HeaderWithData: FC = () => { showRunButton={ !!project && !pathname.startsWith("/link-project") && - (!!actions?.filter((x) => x.enabled).length || + ((project?.path !== "empty" && + !!actions?.filter((x) => x.enabled).length) || (project?.path !== "empty" && !linkedRepository) || (!!linkedRepository && !!actions?.filter((x) => x.enabled).length)) } From 0c836b81a2a3d27775dffa6bb69c9e88b6dd34a5 Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Sat, 29 Jun 2024 23:22:32 +0000 Subject: [PATCH 5/5] update test snapshots --- .../Header/__snapshots__/Header.test.tsx.snap | 100 ++++++++++-------- .../__snapshots__/MainContent.test.tsx.snap | 20 ++-- 2 files changed, 69 insertions(+), 51 deletions(-) diff --git a/webui/src/Components/Header/__snapshots__/Header.test.tsx.snap b/webui/src/Components/Header/__snapshots__/Header.test.tsx.snap index 66a9aba..a83a27b 100644 --- a/webui/src/Components/Header/__snapshots__/Header.test.tsx.snap +++ b/webui/src/Components/Header/__snapshots__/Header.test.tsx.snap @@ -3,60 +3,74 @@ exports[`Header > should render the component 1`] = `
-
+
+
+ +
  • - frosty-hamilton - -
  • - - + + frosty-hamilton + + + + +

    +

    `; diff --git a/webui/src/Containers/Home/MainContent/__snapshots__/MainContent.test.tsx.snap b/webui/src/Containers/Home/MainContent/__snapshots__/MainContent.test.tsx.snap index efac2e6..6d1bd06 100644 --- a/webui/src/Containers/Home/MainContent/__snapshots__/MainContent.test.tsx.snap +++ b/webui/src/Containers/Home/MainContent/__snapshots__/MainContent.test.tsx.snap @@ -22,7 +22,7 @@ exports[`MainContent > should render the component 1`] = `
    should render the component 1`] = ` stoic-leavitt
    /home/tsirysndr/Documents/github/base-pipeline/example
    +