From 4a28f25347addf05708cdff37ecace4139f01779 Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Thu, 18 Jun 2020 02:52:33 +0100 Subject: [PATCH] Add support for read-only mode for web UI. Fixes #402. --- commands/webui.go | 11 +++++--- doc/man/git-bug-webui.1 | 4 +++ doc/md/git-bug_webui.md | 9 ++++--- graphql/config/config.go | 7 +++++ graphql/graphql_test.go | 3 ++- graphql/handler.go | 5 ++-- graphql/resolvers/mutation.go | 24 +++++++++++++++++ graphql/resolvers/repo.go | 8 ++++-- graphql/resolvers/root.go | 14 +++++++--- misc/bash_completion/git-bug | 2 ++ misc/powershell_completion/git-bug | 1 + misc/zsh_completion/git-bug | 3 ++- webui/src/layout/CurrentIdentity.tsx | 30 +++++++++++++-------- webui/src/layout/CurrentIdentityContext.tsx | 6 +++++ webui/src/layout/ReadonlyHidden.tsx | 19 +++++++++++++ webui/src/layout/index.tsx | 6 +++-- webui/src/pages/bug/Bug.tsx | 9 ++++--- webui/src/pages/bug/CommentForm.graphql | 4 ++- 18 files changed, 130 insertions(+), 35 deletions(-) create mode 100644 graphql/config/config.go create mode 100644 webui/src/layout/CurrentIdentityContext.tsx create mode 100644 webui/src/layout/ReadonlyHidden.tsx diff --git a/commands/webui.go b/commands/webui.go index 5d3d4b4a..7a0fb2cd 100644 --- a/commands/webui.go +++ b/commands/webui.go @@ -19,15 +19,17 @@ import ( "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/graphql" + "github.com/MichaelMure/git-bug/graphql/config" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/webui" ) var ( - webUIPort int - webUIOpen bool - webUINoOpen bool + webUIPort int + webUIOpen bool + webUINoOpen bool + webUIReadOnly bool ) const webUIOpenConfigKey = "git-bug.webui.open" @@ -46,7 +48,7 @@ func runWebUI(cmd *cobra.Command, args []string) error { router := mux.NewRouter() - graphqlHandler, err := graphql.NewHandler(repo) + graphqlHandler, err := graphql.NewHandler(repo, config.Config{ReadOnly: webUIReadOnly}) if err != nil { return err } @@ -261,5 +263,6 @@ func init() { webUICmd.Flags().BoolVar(&webUIOpen, "open", false, "Automatically open the web UI in the default browser") webUICmd.Flags().BoolVar(&webUINoOpen, "no-open", false, "Prevent the automatic opening of the web UI in the default browser") webUICmd.Flags().IntVarP(&webUIPort, "port", "p", 0, "Port to listen to (default is random)") + webUICmd.Flags().BoolVar(&webUIReadOnly, "read-only", false, "Whether to run the web UI in read-only mode") } diff --git a/doc/man/git-bug-webui.1 b/doc/man/git-bug-webui.1 index 62d2e5dc..3fc6fc33 100644 --- a/doc/man/git-bug-webui.1 +++ b/doc/man/git-bug-webui.1 @@ -34,6 +34,10 @@ Available git config: \fB\-p\fP, \fB\-\-port\fP=0 Port to listen to (default is random) +.PP +\fB\-\-read\-only\fP[=false] + Whether to run the web UI in read\-only mode + .PP \fB\-h\fP, \fB\-\-help\fP[=false] help for webui diff --git a/doc/md/git-bug_webui.md b/doc/md/git-bug_webui.md index f3ab724a..98a61eb2 100644 --- a/doc/md/git-bug_webui.md +++ b/doc/md/git-bug_webui.md @@ -17,10 +17,11 @@ git-bug webui [flags] ### Options ``` - --open Automatically open the web UI in the default browser - --no-open Prevent the automatic opening of the web UI in the default browser - -p, --port int Port to listen to (default is random) - -h, --help help for webui + --open Automatically open the web UI in the default browser + --no-open Prevent the automatic opening of the web UI in the default browser + -p, --port int Port to listen to (default is random) + --read-only Whether to run the web UI in read-only mode + -h, --help help for webui ``` ### SEE ALSO diff --git a/graphql/config/config.go b/graphql/config/config.go new file mode 100644 index 00000000..2e5f11b3 --- /dev/null +++ b/graphql/config/config.go @@ -0,0 +1,7 @@ +// Package config contains configuration for GraphQL stuff. +package config + +// Config holds configuration elements. +type Config struct { + ReadOnly bool +} diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 0ff2c3fb..7c1ae66b 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -5,6 +5,7 @@ import ( "github.com/99designs/gqlgen/client" + "github.com/MichaelMure/git-bug/graphql/config" "github.com/MichaelMure/git-bug/graphql/models" "github.com/MichaelMure/git-bug/misc/random_bugs" "github.com/MichaelMure/git-bug/repository" @@ -16,7 +17,7 @@ func TestQueries(t *testing.T) { random_bugs.FillRepoWithSeed(repo, 10, 42) - handler, err := NewHandler(repo) + handler, err := NewHandler(repo, config.Config{}) if err != nil { t.Fatal(err) } diff --git a/graphql/handler.go b/graphql/handler.go index 55ef6fc4..a1be7352 100644 --- a/graphql/handler.go +++ b/graphql/handler.go @@ -8,6 +8,7 @@ import ( "github.com/99designs/gqlgen/graphql/handler" + "github.com/MichaelMure/git-bug/graphql/config" "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/resolvers" "github.com/MichaelMure/git-bug/repository" @@ -19,9 +20,9 @@ type Handler struct { *resolvers.RootResolver } -func NewHandler(repo repository.ClockedRepo) (Handler, error) { +func NewHandler(repo repository.ClockedRepo, cfg config.Config) (Handler, error) { h := Handler{ - RootResolver: resolvers.NewRootResolver(), + RootResolver: resolvers.NewRootResolver(cfg), } err := h.RootResolver.RegisterDefaultRepository(repo) diff --git a/graphql/resolvers/mutation.go b/graphql/resolvers/mutation.go index 850645f4..80d6fb1a 100644 --- a/graphql/resolvers/mutation.go +++ b/graphql/resolvers/mutation.go @@ -7,8 +7,32 @@ import ( "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/models" + "github.com/vektah/gqlparser/gqlerror" ) +var _ graph.MutationResolver = &readonlyMutationResolver{} + +type readonlyMutationResolver struct{} + +func (readonlyMutationResolver) NewBug(_ context.Context, _ models.NewBugInput) (*models.NewBugPayload, error) { + return nil, gqlerror.Errorf("readonly mode") +} +func (readonlyMutationResolver) AddComment(_ context.Context, input models.AddCommentInput) (*models.AddCommentPayload, error) { + return nil, gqlerror.Errorf("readonly mode") +} +func (readonlyMutationResolver) ChangeLabels(_ context.Context, input *models.ChangeLabelInput) (*models.ChangeLabelPayload, error) { + return nil, gqlerror.Errorf("readonly mode") +} +func (readonlyMutationResolver) OpenBug(_ context.Context, input models.OpenBugInput) (*models.OpenBugPayload, error) { + return nil, gqlerror.Errorf("readonly mode") +} +func (readonlyMutationResolver) CloseBug(_ context.Context, input models.CloseBugInput) (*models.CloseBugPayload, error) { + return nil, gqlerror.Errorf("readonly mode") +} +func (readonlyMutationResolver) SetTitle(_ context.Context, input models.SetTitleInput) (*models.SetTitlePayload, error) { + return nil, gqlerror.Errorf("readonly mode") +} + var _ graph.MutationResolver = &mutationResolver{} type mutationResolver struct { diff --git a/graphql/resolvers/repo.go b/graphql/resolvers/repo.go index 639e8f90..e30b49f0 100644 --- a/graphql/resolvers/repo.go +++ b/graphql/resolvers/repo.go @@ -5,6 +5,7 @@ import ( "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/graphql/config" "github.com/MichaelMure/git-bug/graphql/connections" "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/models" @@ -13,7 +14,7 @@ import ( var _ graph.RepositoryResolver = &repoResolver{} -type repoResolver struct{} +type repoResolver struct{ cfg config.Config } func (repoResolver) Name(_ context.Context, obj *models.Repository) (*string, error) { name := obj.Repo.Name() @@ -149,7 +150,10 @@ func (repoResolver) Identity(_ context.Context, obj *models.Repository, prefix s return models.NewLazyIdentity(obj.Repo, excerpt), nil } -func (repoResolver) UserIdentity(_ context.Context, obj *models.Repository) (models.IdentityWrapper, error) { +func (r repoResolver) UserIdentity(_ context.Context, obj *models.Repository) (models.IdentityWrapper, error) { + if r.cfg.ReadOnly { + return nil, nil + } excerpt, err := obj.Repo.GetUserIdentityExcerpt() if err != nil { return nil, err diff --git a/graphql/resolvers/root.go b/graphql/resolvers/root.go index 9973ff59..214bbae3 100644 --- a/graphql/resolvers/root.go +++ b/graphql/resolvers/root.go @@ -3,6 +3,7 @@ package resolvers import ( "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/graphql/config" "github.com/MichaelMure/git-bug/graphql/graph" ) @@ -10,11 +11,13 @@ var _ graph.ResolverRoot = &RootResolver{} type RootResolver struct { cache.MultiRepoCache + cfg config.Config } -func NewRootResolver() *RootResolver { +func NewRootResolver(cfg config.Config) *RootResolver { return &RootResolver{ MultiRepoCache: cache.NewMultiRepoCache(), + cfg: cfg, } } @@ -25,13 +28,16 @@ func (r RootResolver) Query() graph.QueryResolver { } func (r RootResolver) Mutation() graph.MutationResolver { + if r.cfg.ReadOnly { + return &readonlyMutationResolver{} + } return &mutationResolver{ cache: &r.MultiRepoCache, } } -func (RootResolver) Repository() graph.RepositoryResolver { - return &repoResolver{} +func (r RootResolver) Repository() graph.RepositoryResolver { + return &repoResolver{r.cfg} } func (RootResolver) Bug() graph.BugResolver { @@ -50,7 +56,7 @@ func (RootResolver) Label() graph.LabelResolver { return &labelResolver{} } -func (r RootResolver) Identity() graph.IdentityResolver { +func (RootResolver) Identity() graph.IdentityResolver { return &identityResolver{} } diff --git a/misc/bash_completion/git-bug b/misc/bash_completion/git-bug index c3e62849..4152725f 100644 --- a/misc/bash_completion/git-bug +++ b/misc/bash_completion/git-bug @@ -1213,6 +1213,8 @@ _git-bug_webui() two_word_flags+=("--port") two_word_flags+=("-p") local_nonpersistent_flags+=("--port=") + flags+=("--read-only") + local_nonpersistent_flags+=("--read-only") must_have_one_flag=() must_have_one_noun=() diff --git a/misc/powershell_completion/git-bug b/misc/powershell_completion/git-bug index 0a8c7b5a..40831a11 100644 --- a/misc/powershell_completion/git-bug +++ b/misc/powershell_completion/git-bug @@ -240,6 +240,7 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock { [CompletionResult]::new('--no-open', 'no-open', [CompletionResultType]::ParameterName, 'Prevent the automatic opening of the web UI in the default browser') [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Port to listen to (default is random)') [CompletionResult]::new('--port', 'port', [CompletionResultType]::ParameterName, 'Port to listen to (default is random)') + [CompletionResult]::new('--read-only', 'read-only', [CompletionResultType]::ParameterName, 'Whether to run the web UI in read-only mode') break } }) diff --git a/misc/zsh_completion/git-bug b/misc/zsh_completion/git-bug index ba15f3bc..fbd52be5 100644 --- a/misc/zsh_completion/git-bug +++ b/misc/zsh_completion/git-bug @@ -459,6 +459,7 @@ function _git-bug_webui { _arguments \ '--open[Automatically open the web UI in the default browser]' \ '--no-open[Prevent the automatic opening of the web UI in the default browser]' \ - '(-p --port)'{-p,--port}'[Port to listen to (default is random)]:' + '(-p --port)'{-p,--port}'[Port to listen to (default is random)]:' \ + '--read-only[Whether to run the web UI in read-only mode]' } diff --git a/webui/src/layout/CurrentIdentity.tsx b/webui/src/layout/CurrentIdentity.tsx index 21f489ef..55060179 100644 --- a/webui/src/layout/CurrentIdentity.tsx +++ b/webui/src/layout/CurrentIdentity.tsx @@ -3,7 +3,7 @@ import React from 'react'; import Avatar from '@material-ui/core/Avatar'; import { makeStyles } from '@material-ui/core/styles'; -import { useCurrentIdentityQuery } from './CurrentIdentity.generated'; +import CurrentIdentityContext from './CurrentIdentityContext'; const useStyles = makeStyles(theme => ({ displayName: { @@ -13,18 +13,26 @@ const useStyles = makeStyles(theme => ({ const CurrentIdentity = () => { const classes = useStyles(); - const { loading, error, data } = useCurrentIdentityQuery(); - if (error || loading || !data?.repository?.userIdentity) return null; - - const user = data.repository.userIdentity; return ( - <> - - {user.displayName.charAt(0).toUpperCase()} - -
{user.displayName}
- + + {context => { + if (!context) return null; + const { loading, error, data } = context as any; + + if (error || loading || !data?.repository?.userIdentity) return null; + + const user = data.repository.userIdentity; + return ( + <> + + {user.displayName.charAt(0).toUpperCase()} + +
{user.displayName}
+ + ); + }} +
); }; diff --git a/webui/src/layout/CurrentIdentityContext.tsx b/webui/src/layout/CurrentIdentityContext.tsx new file mode 100644 index 00000000..78f2f263 --- /dev/null +++ b/webui/src/layout/CurrentIdentityContext.tsx @@ -0,0 +1,6 @@ +import React from 'react'; + +import { CurrentIdentityQueryResult } from './CurrentIdentity.generated'; + +const Context = React.createContext(null as CurrentIdentityQueryResult | null); +export default Context; diff --git a/webui/src/layout/ReadonlyHidden.tsx b/webui/src/layout/ReadonlyHidden.tsx new file mode 100644 index 00000000..9ed0af6a --- /dev/null +++ b/webui/src/layout/ReadonlyHidden.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +import CurrentIdentityContext from './CurrentIdentityContext'; + +type Props = { children: React.ReactNode }; +const ReadonlyHidden = ({ children }: Props) => ( + + {context => { + if (!context) return null; + const { loading, error, data } = context; + + if (error || loading || !data?.repository?.userIdentity) return null; + + return <>{children}; + }} + +); + +export default ReadonlyHidden; diff --git a/webui/src/layout/index.tsx b/webui/src/layout/index.tsx index 42a0cfc1..78ff5ae8 100644 --- a/webui/src/layout/index.tsx +++ b/webui/src/layout/index.tsx @@ -2,16 +2,18 @@ import React from 'react'; import CssBaseline from '@material-ui/core/CssBaseline'; +import { useCurrentIdentityQuery } from './CurrentIdentity.generated'; +import CurrentIdentityContext from './CurrentIdentityContext'; import Header from './Header'; type Props = { children: React.ReactNode }; function Layout({ children }: Props) { return ( - <> +
{children} - + ); } diff --git a/webui/src/pages/bug/Bug.tsx b/webui/src/pages/bug/Bug.tsx index 1bc128dd..99b9bddd 100644 --- a/webui/src/pages/bug/Bug.tsx +++ b/webui/src/pages/bug/Bug.tsx @@ -6,6 +6,7 @@ import { makeStyles } from '@material-ui/core/styles'; import Author from 'src/components/Author'; import Date from 'src/components/Date'; import Label from 'src/components/Label'; +import ReadonlyHidden from 'src/layout/ReadonlyHidden'; import { BugFragment } from './Bug.generated'; import CommentForm from './CommentForm'; @@ -88,9 +89,11 @@ function Bug({ bug }: Props) {
-
- -
+ +
+ +
+
Labels diff --git a/webui/src/pages/bug/CommentForm.graphql b/webui/src/pages/bug/CommentForm.graphql index 33d21193..f4b61850 100644 --- a/webui/src/pages/bug/CommentForm.graphql +++ b/webui/src/pages/bug/CommentForm.graphql @@ -1,5 +1,7 @@ mutation AddComment($input: AddCommentInput!) { addComment(input: $input) { - operation { id } + operation { + id + } } }