aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuke Granger-Brown <git@lukegb.com>2020-06-18 02:52:33 +0100
committerMichael Muré <batolettre@gmail.com>2020-06-27 22:56:10 +0200
commit4a28f25347addf05708cdff37ecace4139f01779 (patch)
tree145e5fd420f70b182d66a5824d76300b5307d509
parent23228101a2a38a139f6fc2cafc18e9f08d911089 (diff)
downloadgit-bug-4a28f25347addf05708cdff37ecace4139f01779.tar.gz
Add support for read-only mode for web UI.
Fixes #402.
-rw-r--r--commands/webui.go11
-rw-r--r--doc/man/git-bug-webui.14
-rw-r--r--doc/md/git-bug_webui.md9
-rw-r--r--graphql/config/config.go7
-rw-r--r--graphql/graphql_test.go3
-rw-r--r--graphql/handler.go5
-rw-r--r--graphql/resolvers/mutation.go24
-rw-r--r--graphql/resolvers/repo.go8
-rw-r--r--graphql/resolvers/root.go14
-rw-r--r--misc/bash_completion/git-bug2
-rw-r--r--misc/powershell_completion/git-bug1
-rw-r--r--misc/zsh_completion/git-bug3
-rw-r--r--webui/src/layout/CurrentIdentity.tsx30
-rw-r--r--webui/src/layout/CurrentIdentityContext.tsx6
-rw-r--r--webui/src/layout/ReadonlyHidden.tsx19
-rw-r--r--webui/src/layout/index.tsx6
-rw-r--r--webui/src/pages/bug/Bug.tsx9
-rw-r--r--webui/src/pages/bug/CommentForm.graphql4
18 files changed, 130 insertions, 35 deletions
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
@@ -35,6 +35,10 @@ Available git config:
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 (
- <>
- <Avatar src={user.avatarUrl ? user.avatarUrl : undefined}>
- {user.displayName.charAt(0).toUpperCase()}
- </Avatar>
- <div className={classes.displayName}>{user.displayName}</div>
- </>
+ <CurrentIdentityContext.Consumer>
+ {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 (
+ <>
+ <Avatar src={user.avatarUrl ? user.avatarUrl : undefined}>
+ {user.displayName.charAt(0).toUpperCase()}
+ </Avatar>
+ <div className={classes.displayName}>{user.displayName}</div>
+ </>
+ );
+ }}
+ </CurrentIdentityContext.Consumer>
);
};
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) => (
+ <CurrentIdentityContext.Consumer>
+ {context => {
+ if (!context) return null;
+ const { loading, error, data } = context;
+
+ if (error || loading || !data?.repository?.userIdentity) return null;
+
+ return <>{children}</>;
+ }}
+ </CurrentIdentityContext.Consumer>
+);
+
+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 (
- <>
+ <CurrentIdentityContext.Provider value={useCurrentIdentityQuery()}>
<CssBaseline />
<Header />
{children}
- </>
+ </CurrentIdentityContext.Provider>
);
}
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) {
<div className={classes.container}>
<div className={classes.timeline}>
<TimelineQuery id={bug.id} />
- <div className={classes.commentForm}>
- <CommentForm bugId={bug.id} />
- </div>
+ <ReadonlyHidden>
+ <div className={classes.commentForm}>
+ <CommentForm bugId={bug.id} />
+ </div>
+ </ReadonlyHidden>
</div>
<div className={classes.sidebar}>
<span className={classes.sidebarTitle}>Labels</span>
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
+ }
}
}