diff --git a/api/dvmodel.proto b/api/dvmodel.proto index 186a5d17f..c25344ff8 100644 --- a/api/dvmodel.proto +++ b/api/dvmodel.proto @@ -145,8 +145,9 @@ message Topic { enum Driver { UnknownDriver = 0; GitHub = 1; - //GitLab = 2; - //Trello = 3; + Trello = 2; + //GitLab = 3; + //Jira = 4; } diff --git a/cmd/depviz/main.go b/cmd/depviz/main.go index 4a1562388..751214964 100644 --- a/cmd/depviz/main.go +++ b/cmd/depviz/main.go @@ -53,6 +53,8 @@ var ( serverShutdownTimeout = serverFlags.Duration("shutdowm-timeout", 6*time.Second, "shutdown timeout") // nolint:gomnd serverCORSAllowedOrigins = serverFlags.String("cors-allowed-origins", "*", "allowed CORS origins") serverGitHubToken = serverFlags.String("github-token", "", "GitHub token") + serverTrelloToken = serverFlags.String("trello-token", "", "Trello token") + serverTrelloApiKey = serverFlags.String("trello-apikey", "", "Trello ApiKey") serverNoAutoUpdate = serverFlags.Bool("no-auto-update", false, "don't auto-update projects in background") serverGodmode = serverFlags.Bool("godmode", false, "enable dangerous API calls") serverWithPprof = serverFlags.Bool("with-pprof", false, "enable pprof endpoints") @@ -69,6 +71,8 @@ var ( runNoGraph = runFlags.Bool("no-graph", false, "don't generate graph (pull only)") runResync = runFlags.Bool("resync", false, "resync already synced content") runGitHubToken = runFlags.String("github-token", "", "GitHub token") + runTrelloToken = runFlags.String("trello-token", "", "Trello token") + runTrelloApikey = runFlags.String("trello-apikey", "", "Trello ApiKey") runNoPert = runFlags.Bool("no-pert", false, "disable PERT computing") runFormat = runFlags.String("format", "dot", "output format") runVertical = runFlags.Bool("vertical", false, "vertical mode") @@ -270,6 +274,8 @@ func execRun(ctx context.Context, args []string) error { NoPull: *runNoPull, Format: *runFormat, Resync: *runResync, + TrelloToken: *runTrelloToken, + TrelloApiKey: *runTrelloApikey, GitHubToken: *runGitHubToken, ShowClosed: *runShowClosed, HideIsolated: *runHideIsolated, @@ -314,6 +320,8 @@ func execServer(ctx context.Context, args []string) error { Realm: *serverRealm, Godmode: *serverGodmode, GitHubToken: *serverGitHubToken, + TrelloToken: *serverTrelloToken, + TrelloApiKey: *serverTrelloApiKey, NoAutoUpdate: *serverNoAutoUpdate, AutoUpdateTargets: targets, AutoUpdateInterval: *serverAutoUpdateInterval, diff --git a/go.mod b/go.mod index 61692df83..d542683ab 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ replace github.com/brianloveswords/airtable => github.com/moul/brianloveswords-a require ( github.com/Bearer/bearer-go v1.2.1 + github.com/adlio/trello v1.10.0 github.com/cayleygraph/cayley v0.7.7 github.com/cayleygraph/quad v1.2.4 github.com/go-chi/chi v4.1.2+incompatible @@ -23,6 +24,7 @@ require ( github.com/rs/cors v1.7.0 github.com/stretchr/testify v1.8.0 github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 + github.com/tidwall/gjson v1.14.3 github.com/treastech/logger v0.0.0-20180705232552-e381e9ecf2e3 github.com/xhit/go-str2duration/v2 v2.0.0 go.uber.org/zap v1.17.0 @@ -61,6 +63,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/piprate/json-gold v0.3.0 // indirect github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect github.com/prometheus/client_golang v1.8.0 // indirect @@ -71,6 +74,8 @@ require ( github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/cobra v1.2.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect github.com/tylertreat/BoomFilters v0.0.0-20200520150052-42a7b4300c0c // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.7.0 // indirect @@ -81,6 +86,7 @@ require ( golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index ca8d1f8dc..3965fceb4 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/adlio/trello v1.10.0 h1:ia/rzoBwJJKr4IqnMlrU6n09CVqeyaahSkEVcV5/gPc= +github.com/adlio/trello v1.10.0/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= @@ -589,7 +591,13 @@ github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpP github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 h1:34icjjmqJ2HPjrSuJYEkdZ+0ItmGQAQ75cRHIiftIyE= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= +github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= +github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/treastech/logger v0.0.0-20180705232552-e381e9ecf2e3 h1:0SnC9653NEySn3YUL1UV9o45KfQzszcOpIJ2f2BlrVg= @@ -854,6 +862,8 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/dvcore/run.go b/internal/dvcore/run.go index ffe28fcc0..525245dbf 100644 --- a/internal/dvcore/run.go +++ b/internal/dvcore/run.go @@ -15,6 +15,7 @@ import ( "moul.io/depviz/v3/internal/dvparser" "moul.io/depviz/v3/internal/dvstore" "moul.io/depviz/v3/internal/githubprovider" + "moul.io/depviz/v3/internal/trelloprovider" "moul.io/graphman" "moul.io/graphman/viz" "moul.io/multipmuri" @@ -30,9 +31,10 @@ type RunOpts struct { // pull - GitHubToken string + GitHubToken string + TrelloToken string + TrelloApiKey string // GitLabToken string - // TrelloToken string // JiraToken string Resync bool @@ -61,7 +63,7 @@ func Run(h *cayley.Handle, args []string, opts RunOpts) error { } if !opts.NoPull { - _, err := PullAndSave(targets, h, opts.Schema, opts.GitHubToken, opts.Resync, opts.Logger) + _, err := PullAndSave(targets, h, opts.Schema, opts.GitHubToken, opts.TrelloToken, opts.TrelloApiKey, opts.Resync, opts.Logger) if err != nil { return fmt.Errorf("pull: %w", err) } @@ -76,7 +78,7 @@ func Run(h *cayley.Handle, args []string, opts RunOpts) error { WithoutPRs: opts.HidePRs, WithoutExternalDeps: opts.HideExternalDeps, } - tasks, err := dvstore.LoadTasks(h, opts.Schema, filters, opts.Logger) + tasks, err := dvstore.LoadTasks(h, opts.Schema, opts.TrelloToken, opts.TrelloApiKey, filters, opts.Logger) if err != nil { return fmt.Errorf("load tasks: %w", err) } @@ -152,8 +154,8 @@ func Run(h *cayley.Handle, args []string, opts RunOpts) error { return nil } -func PullAndSave(targets []multipmuri.Entity, h *cayley.Handle, schema *schema.Config, githubToken string, resync bool, logger *zap.Logger) (bool, error) { - batches := pullBatches(targets, h, githubToken, resync, logger) +func PullAndSave(targets []multipmuri.Entity, h *cayley.Handle, schema *schema.Config, githubToken string, trelloToken string, trelloApiKey string, resync bool, logger *zap.Logger) (bool, error) { + batches := pullBatches(targets, h, githubToken, trelloToken, trelloApiKey, resync, logger) if len(batches) > 0 { err := saveBatches(h, schema, batches) if err != nil { @@ -164,7 +166,7 @@ func PullAndSave(targets []multipmuri.Entity, h *cayley.Handle, schema *schema.C return false, nil } -func pullBatches(targets []multipmuri.Entity, h *cayley.Handle, githubToken string, resync bool, logger *zap.Logger) []dvmodel.Batch { +func pullBatches(targets []multipmuri.Entity, h *cayley.Handle, githubToken string, trelloToken string, trelloApiKey string, resync bool, logger *zap.Logger) []dvmodel.Batch { // FIXME: handle the special '@me' target var ( wg sync.WaitGroup @@ -198,6 +200,16 @@ func pullBatches(targets []multipmuri.Entity, h *cayley.Handle, githubToken stri githubprovider.FetchRepo(ctx, repo, githubToken, out, ghOpts) }(target) + case multipmuri.TrelloProvider: + go func(board multipmuri.Entity) { + defer wg.Done() + + ghOpts := trelloprovider.Opts{ + Logger: logger.Named("trello"), + } + + trelloprovider.FetchCard(ctx, board, trelloToken, trelloApiKey, target.LocalID()[1:], out, ghOpts) + }(target) default: // FIXME: clean context-based exit panic(fmt.Sprintf("unsupported provider: %v", provider)) @@ -307,6 +319,18 @@ func graphmanPertConfig(tasks []dvmodel.Task, opts RunOpts) *graphman.PertConfig // FIXME: styling }, ) + case dvmodel.Task_Card: + config.States = append( + config.States, + graphman.PertState{ + ID: string(task.ID), + Title: task.Title, + DependsOn: dependsOn, + // FIXME: auto estimate (PERT) + // FIXME: DependsOn: milestone.DependsOn + // FIXME: styling + }, + ) default: opts.Logger.Warn("unsupported task kind", zap.Stringer("kind", task.Kind)) } diff --git a/internal/dvmodel/dvmodel.pb.go b/internal/dvmodel/dvmodel.pb.go index 3ba978b67..bde0ac182 100644 --- a/internal/dvmodel/dvmodel.pb.go +++ b/internal/dvmodel/dvmodel.pb.go @@ -36,16 +36,19 @@ type Driver int32 const ( Driver_UnknownDriver Driver = 0 Driver_GitHub Driver = 1 + Driver_Trello Driver = 2 ) var Driver_name = map[int32]string{ 0: "UnknownDriver", 1: "GitHub", + 2: "Trello", } var Driver_value = map[string]int32{ "UnknownDriver": 0, "GitHub": 1, + "Trello": 2, } func (x Driver) String() string { @@ -450,101 +453,102 @@ func init() { proto.RegisterFile("dvmodel.proto", fileDescriptor_106647ce772da30 func init() { golang_proto.RegisterFile("dvmodel.proto", fileDescriptor_106647ce772da30c) } var fileDescriptor_106647ce772da30c = []byte{ - // 1503 bytes of a gzipped FileDescriptorProto + // 1510 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x58, 0x41, 0x6f, 0xdb, 0x46, 0x16, 0x16, 0x65, 0x4b, 0xb1, 0x9e, 0xe4, 0x98, 0x9e, 0x24, 0x58, 0xae, 0x77, 0x57, 0x54, 0x94, - 0xdd, 0x8d, 0xda, 0x26, 0x12, 0x9a, 0x02, 0x29, 0x10, 0xa0, 0x48, 0x2c, 0xbb, 0x49, 0x84, 0x3a, - 0xb5, 0xa1, 0xd8, 0x28, 0x90, 0xa6, 0x10, 0x46, 0xe2, 0x58, 0x9a, 0x8a, 0xe4, 0x30, 0x1c, 0xd2, - 0x46, 0xf2, 0x17, 0x7a, 0xc9, 0xa1, 0x3f, 0xa2, 0x3f, 0xa3, 0x47, 0x1f, 0x7a, 0xc8, 0xb1, 0x27, - 0xb6, 0xb1, 0xff, 0x81, 0x4e, 0x45, 0x4f, 0xc5, 0x0c, 0x49, 0x89, 0xb4, 0x94, 0x34, 0x0a, 0xdc, - 0xf4, 0x92, 0x9b, 0xf8, 0xde, 0xf7, 0xbe, 0xef, 0x0d, 0xdf, 0xcc, 0x9b, 0x47, 0xc1, 0xb2, 0x71, - 0x60, 0x31, 0x83, 0x98, 0x75, 0xc7, 0x65, 0x1e, 0x43, 0x25, 0x83, 0x38, 0x07, 0xf4, 0x59, 0x5d, - 0xda, 0xd6, 0xf4, 0x3e, 0x63, 0x7d, 0x93, 0x34, 0xa4, 0xaf, 0xeb, 0xef, 0x37, 0x3c, 0x6a, 0x11, - 0xee, 0x61, 0xcb, 0x09, 0xe1, 0x6b, 0xd7, 0xfb, 0xd4, 0x1b, 0xf8, 0xdd, 0x7a, 0x8f, 0x59, 0x8d, - 0x3e, 0xeb, 0xb3, 0x09, 0x52, 0x3c, 0xc9, 0x07, 0xf9, 0x2b, 0x84, 0x57, 0x7f, 0x2a, 0x40, 0x6e, - 0xfb, 0xd0, 0x26, 0x2e, 0xba, 0x07, 0x59, 0x6a, 0x68, 0x4a, 0x45, 0xa9, 0x15, 0x9a, 0x9f, 0x1e, - 0x07, 0x7a, 0xb6, 0xb5, 0x39, 0x0a, 0x74, 0x78, 0xe2, 0x63, 0xe3, 0x56, 0xf5, 0x0e, 0x35, 0xaa, - 0xbf, 0x07, 0xba, 0x9e, 0x20, 0xef, 0xe1, 0xa7, 0x26, 0x79, 0xda, 0x77, 0xb1, 0x33, 0x68, 0x08, - 0x50, 0xbd, 0xd5, 0x6e, 0xb5, 0xb3, 0xd4, 0x40, 0x7d, 0x80, 0x9e, 0x4b, 0xb0, 0x47, 0x8c, 0x0e, - 0xf6, 0xb4, 0x85, 0x8a, 0x52, 0x2b, 0xde, 0x58, 0xab, 0x87, 0x79, 0xd7, 0xe3, 0x6c, 0xea, 0xbb, - 0x71, 0xde, 0xcd, 0x6b, 0x47, 0x81, 0xae, 0x8c, 0x02, 0xbd, 0x12, 0x4a, 0xf1, 0xde, 0x80, 0x58, - 0xf8, 0x56, 0x44, 0xb1, 0xee, 0x5d, 0x63, 0x8e, 0x47, 0x99, 0x8d, 0xcd, 0xea, 0xf3, 0x5f, 0x74, - 0xa5, 0x5d, 0x18, 0x3b, 0x84, 0x90, 0xef, 0x18, 0xb1, 0xd0, 0xe2, 0x5b, 0x0a, 0x45, 0x14, 0xd3, - 0x42, 0x63, 0x07, 0xba, 0x0f, 0x4b, 0x26, 0xeb, 0x61, 0xb3, 0x43, 0x0d, 0x2d, 0x27, 0x5f, 0xd0, - 0xf5, 0xe3, 0x40, 0x3f, 0xb7, 0x25, 0x6c, 0xf2, 0x2d, 0x95, 0x53, 0x8c, 0x12, 0xdb, 0x32, 0x26, - 0x7c, 0xed, 0x73, 0x91, 0x09, 0x3d, 0x80, 0xc5, 0x21, 0xb5, 0x0d, 0x0d, 0x2a, 0x4a, 0xed, 0xfc, - 0x0d, 0xad, 0x9e, 0xac, 0x6d, 0x5d, 0xd6, 0xa1, 0xfe, 0x05, 0xb5, 0x8d, 0xa6, 0x3e, 0x0a, 0xf4, - 0x7f, 0xa5, 0x48, 0x45, 0x58, 0x82, 0x51, 0xd2, 0xa0, 0x0d, 0x00, 0x3e, 0x60, 0xae, 0xd7, 0xb1, - 0xb1, 0x45, 0xb4, 0xa2, 0x4c, 0xed, 0xbf, 0x53, 0x2b, 0x94, 0x90, 0x2f, 0xb1, 0x45, 0x12, 0xf1, - 0x85, 0xb1, 0x11, 0xdd, 0x81, 0xc2, 0xbe, 0x6f, 0x9a, 0x21, 0x47, 0x49, 0x72, 0x5c, 0x19, 0x05, - 0xba, 0x9e, 0xe2, 0x10, 0x88, 0x53, 0x14, 0x4b, 0xb1, 0x0d, 0x6d, 0x43, 0xde, 0x70, 0xe9, 0x01, - 0x71, 0xb5, 0x65, 0xb9, 0xae, 0x8b, 0xe9, 0x75, 0x6d, 0x4a, 0x5f, 0xf3, 0xf2, 0x28, 0xd0, 0xff, - 0x93, 0x22, 0x0d, 0x83, 0x12, 0x94, 0x11, 0x0d, 0xba, 0x0d, 0x4b, 0x03, 0x66, 0x11, 0x07, 0xf7, - 0x89, 0x76, 0xfe, 0x15, 0x19, 0xc5, 0x80, 0x64, 0x46, 0xb1, 0x0d, 0xdd, 0x87, 0xa2, 0x41, 0x78, - 0xcf, 0xa5, 0xd2, 0xa7, 0xad, 0x48, 0x8e, 0xff, 0x8f, 0x02, 0xbd, 0x9a, 0x4e, 0x60, 0x82, 0x49, - 0xd0, 0x24, 0x43, 0xd1, 0x3e, 0x14, 0xf7, 0x99, 0x3b, 0xec, 0x70, 0x0f, 0x7b, 0x3e, 0xd7, 0x54, - 0xb9, 0xc0, 0xf2, 0xac, 0xc2, 0xdd, 0x65, 0xee, 0xf0, 0xa1, 0x44, 0x35, 0xff, 0x37, 0x0a, 0xf4, - 0xcb, 0xe9, 0xf7, 0x37, 0x76, 0x26, 0x84, 0x60, 0x62, 0x45, 0x3b, 0x00, 0xf8, 0x00, 0x7b, 0xd8, - 0xed, 0xf8, 0xae, 0xa9, 0xad, 0xca, 0x84, 0x3f, 0x3e, 0x0e, 0xf4, 0xc2, 0xba, 0xb4, 0xee, 0xb5, - 0xb7, 0xa6, 0xea, 0x1a, 0xe2, 0xf7, 0x5c, 0x33, 0x59, 0xd7, 0xb1, 0x11, 0x3d, 0x86, 0xc2, 0x00, - 0xf3, 0x0e, 0x13, 0xc9, 0x69, 0x86, 0x24, 0xbc, 0x3d, 0x0a, 0x74, 0x2d, 0xe4, 0x18, 0x60, 0x2e, - 0xd3, 0x9e, 0xc4, 0xbe, 0xc9, 0xf9, 0x5e, 0x8a, 0xc3, 0xaa, 0x7b, 0xb0, 0x28, 0x76, 0x2a, 0x5a, - 0x81, 0xe2, 0x9e, 0x3d, 0xb4, 0xd9, 0xa1, 0x2d, 0x1e, 0xd5, 0x0c, 0x5a, 0x82, 0xc5, 0x3d, 0x4e, - 0x5c, 0x55, 0x41, 0x2a, 0x94, 0xb6, 0xdd, 0x3e, 0xb6, 0xe9, 0x33, 0x2c, 0x24, 0xd4, 0xac, 0xf0, - 0xed, 0x12, 0x6c, 0xa9, 0x0b, 0xe2, 0x57, 0x9b, 0x38, 0x4c, 0x5d, 0x44, 0x25, 0x58, 0xda, 0x71, - 0xd9, 0x01, 0x35, 0x88, 0xab, 0xe6, 0xaa, 0x9f, 0x01, 0x4c, 0xde, 0x23, 0xba, 0x04, 0xab, 0x11, - 0xf9, 0xc4, 0xa8, 0x66, 0x10, 0x40, 0xbe, 0xc5, 0x85, 0x45, 0x55, 0x44, 0x78, 0x8b, 0x3f, 0x64, - 0xbe, 0xdb, 0x23, 0x6a, 0xb6, 0xfa, 0xfd, 0x45, 0x58, 0xdc, 0xc5, 0x7c, 0xf8, 0xbe, 0x9b, 0xbd, - 0x8b, 0x6e, 0xb6, 0x95, 0xea, 0x66, 0xff, 0x48, 0x1f, 0x0a, 0x51, 0x86, 0xb9, 0x9a, 0xd9, 0x4d, - 0xc8, 0x79, 0xd4, 0x33, 0xe3, 0x3e, 0x56, 0x19, 0x05, 0xfa, 0xbf, 0x53, 0x51, 0xd2, 0x9b, 0x08, - 0x0b, 0xe1, 0xa7, 0xcf, 0x7a, 0xe9, 0xed, 0xcf, 0xfa, 0x99, 0xf7, 0xb1, 0xaf, 0x21, 0x6f, 0xf8, - 0xa4, 0xc3, 0x6c, 0xd9, 0xc5, 0x5e, 0x5f, 0xcf, 0x5a, 0x54, 0xcf, 0xf4, 0x9a, 0x0d, 0x9f, 0x6c, - 0xdb, 0xa7, 0x6a, 0x99, 0x93, 0x46, 0x64, 0x41, 0xa9, 0xc7, 0x2c, 0xc7, 0x24, 0xd1, 0x96, 0x59, - 0xf9, 0x53, 0x89, 0x7a, 0x24, 0x91, 0x7e, 0x31, 0x63, 0x92, 0xa9, 0x4d, 0x53, 0x4c, 0xb8, 0xd0, - 0x0e, 0xe4, 0x44, 0x0f, 0x24, 0x51, 0x0b, 0xd4, 0x66, 0x54, 0x5b, 0x1c, 0x50, 0x32, 0xa3, 0x70, - 0x32, 0x2e, 0x59, 0x38, 0x69, 0x40, 0x9b, 0x50, 0xa0, 0xbc, 0x63, 0xb2, 0xde, 0x90, 0x18, 0xb2, - 0xe3, 0x2d, 0x35, 0xaf, 0x46, 0x19, 0xa6, 0x5b, 0x3d, 0xe5, 0x5b, 0x12, 0x94, 0x6c, 0xf5, 0xb1, - 0x0d, 0xb5, 0xa0, 0x64, 0xfb, 0x56, 0xa7, 0xc7, 0x2c, 0x8b, 0xd8, 0x1e, 0xd7, 0x50, 0x45, 0xa9, - 0xe5, 0x66, 0xd4, 0xdf, 0xf6, 0xad, 0x8d, 0x08, 0x93, 0xac, 0x7f, 0xc2, 0x8c, 0xee, 0x82, 0x78, - 0xec, 0xf8, 0xce, 0x01, 0xf3, 0x08, 0xd7, 0x2e, 0x48, 0xa6, 0xe9, 0x5e, 0x6e, 0xfb, 0xd6, 0x5e, - 0x08, 0x49, 0xf6, 0xf2, 0x89, 0x15, 0x6d, 0xc1, 0xb2, 0xe0, 0x31, 0xd8, 0xa1, 0x1d, 0x32, 0x5d, - 0x94, 0x4c, 0x57, 0x47, 0x81, 0x7e, 0xe5, 0x34, 0xd3, 0x66, 0x0c, 0x4a, 0x70, 0x95, 0x92, 0x76, - 0xf4, 0x18, 0x10, 0xe1, 0x1e, 0xb5, 0x64, 0x6b, 0x30, 0x7c, 0x57, 0x36, 0x53, 0xed, 0x52, 0x78, - 0x72, 0x47, 0x81, 0xfe, 0x41, 0x8a, 0x72, 0x1a, 0x9a, 0x20, 0x5e, 0x1d, 0x7b, 0x37, 0x23, 0x27, - 0xea, 0x00, 0x88, 0x5b, 0x02, 0xfb, 0xde, 0x80, 0xc5, 0xd7, 0xc4, 0x9d, 0x51, 0xa0, 0xff, 0x73, - 0x7c, 0x4d, 0xac, 0x4b, 0xd7, 0x7c, 0xf7, 0x44, 0x61, 0x1c, 0x97, 0xbe, 0x86, 0xc8, 0x19, 0x5f, - 0x43, 0x68, 0x00, 0xcb, 0x82, 0xdd, 0xa2, 0x26, 0xe1, 0x1e, 0xb3, 0x89, 0xb6, 0x2f, 0x15, 0x36, - 0x26, 0x7b, 0x70, 0x80, 0xf9, 0x83, 0xd8, 0x3b, 0x9f, 0x4a, 0x29, 0x19, 0x8a, 0x08, 0x94, 0xe4, - 0x8b, 0xe2, 0x9c, 0xf6, 0x6d, 0x42, 0xb4, 0x7e, 0x65, 0xa1, 0x56, 0x68, 0x36, 0x27, 0xbd, 0x4d, - 0x2c, 0x39, 0x72, 0xce, 0xa7, 0x53, 0x4c, 0x44, 0xc6, 0x32, 0x2e, 0x39, 0xa0, 0xe4, 0x90, 0xb8, - 0xda, 0x60, 0x86, 0x4c, 0x3b, 0x72, 0xce, 0x2f, 0x13, 0x47, 0xc6, 0x55, 0x31, 0x71, 0x97, 0x98, - 0x1a, 0x95, 0x1a, 0xe9, 0xaa, 0x6c, 0x09, 0xcf, 0xfc, 0x55, 0x91, 0x61, 0xc8, 0x84, 0x15, 0xca, - 0x3b, 0x06, 0x71, 0x88, 0x6d, 0x50, 0xbb, 0x2f, 0x1a, 0xe0, 0xb7, 0x52, 0x63, 0x73, 0xd2, 0x3b, - 0x29, 0xdf, 0x8c, 0xfd, 0xc9, 0x0e, 0xf7, 0x26, 0x42, 0xcb, 0xa9, 0x58, 0xd4, 0x85, 0x22, 0xe5, - 0x9d, 0xae, 0x68, 0x24, 0xd4, 0xee, 0x6b, 0x43, 0xa9, 0xb4, 0x3e, 0x0a, 0xf4, 0xb5, 0x58, 0xa9, - 0x19, 0xf9, 0xe6, 0x93, 0x81, 0x49, 0x60, 0xb4, 0x22, 0x97, 0x98, 0xf2, 0x68, 0x1d, 0x52, 0x6f, - 0xa0, 0x99, 0xd3, 0x2b, 0x6a, 0x87, 0xfe, 0xaf, 0xa8, 0x37, 0x98, 0x7b, 0x45, 0x89, 0x58, 0xf4, - 0x0d, 0x00, 0xe5, 0x1d, 0x07, 0xbb, 0x5e, 0x87, 0xed, 0x6b, 0xd6, 0xe9, 0xf2, 0x50, 0xbe, 0x83, - 0x5d, 0x6f, 0x7b, 0x7f, 0xce, 0xf2, 0xc4, 0x61, 0xe8, 0x11, 0x88, 0x52, 0x49, 0x7e, 0xcd, 0x3e, - 0x1b, 0xf2, 0x73, 0x03, 0x2c, 0xe3, 0xaa, 0xdd, 0x57, 0xcd, 0x85, 0x05, 0xc8, 0xb5, 0x38, 0xf7, - 0x49, 0x38, 0x18, 0x3e, 0x20, 0x6e, 0x9f, 0xb4, 0xc9, 0x13, 0x9f, 0x70, 0x4f, 0xcd, 0xa2, 0x65, - 0x28, 0x8c, 0x4f, 0x5a, 0x38, 0x1d, 0x7e, 0xee, 0xd0, 0x9e, 0xba, 0x28, 0xa2, 0x1e, 0x7a, 0xcc, - 0x7d, 0xaa, 0xe6, 0x84, 0x71, 0x03, 0xbb, 0x86, 0x9a, 0xaf, 0x36, 0x84, 0x51, 0xdc, 0x20, 0x2a, - 0x94, 0x22, 0x11, 0xf9, 0x1c, 0x4e, 0x9f, 0xdb, 0x0e, 0xb1, 0x55, 0x45, 0x0c, 0x89, 0x1b, 0x26, - 0xe3, 0xc4, 0x50, 0xb3, 0xd5, 0xa3, 0x3c, 0xe4, 0x76, 0x99, 0x43, 0x7b, 0xef, 0xe7, 0xc2, 0xbf, - 0xfd, 0x2b, 0x57, 0xd6, 0xe1, 0x9d, 0x0c, 0x86, 0x93, 0x71, 0xae, 0x74, 0x36, 0xe3, 0xdc, 0x4d, - 0xc8, 0xf5, 0x98, 0xc9, 0xc2, 0xf1, 0x70, 0x56, 0x22, 0xd2, 0x9b, 0x4c, 0x44, 0x1a, 0x4e, 0x4f, - 0xa8, 0xe7, 0xdf, 0x7e, 0x42, 0xfd, 0x6b, 0xbf, 0xe9, 0xaa, 0xaf, 0x39, 0xbb, 0xb2, 0xb1, 0xab, - 0x4a, 0xf5, 0x3b, 0x05, 0x72, 0x4d, 0xec, 0xf5, 0x06, 0xa8, 0x06, 0x39, 0x0f, 0xf3, 0x21, 0xd7, - 0x94, 0xca, 0x42, 0xad, 0x78, 0x03, 0x4d, 0x0f, 0x84, 0xed, 0x10, 0x80, 0x3e, 0x82, 0xbc, 0xcc, - 0x98, 0x6b, 0x59, 0x09, 0xbd, 0x30, 0xe3, 0xf3, 0xb9, 0x1d, 0x41, 0x04, 0xd8, 0x13, 0x5b, 0x84, - 0x6b, 0x0b, 0xb3, 0xc0, 0x72, 0xfb, 0xb4, 0x23, 0xc8, 0x87, 0x57, 0x21, 0x1f, 0x96, 0x11, 0xad, - 0xc2, 0x72, 0x94, 0x73, 0x68, 0x08, 0x3f, 0x13, 0xef, 0x51, 0xef, 0xbe, 0xdf, 0x55, 0x95, 0x66, - 0xeb, 0xe8, 0x65, 0x39, 0xf3, 0xdb, 0xcb, 0x72, 0xe6, 0x87, 0xe3, 0x72, 0xe6, 0xe8, 0xb8, 0xac, - 0xbc, 0x38, 0x2e, 0x2b, 0xbf, 0x1e, 0x97, 0x95, 0xe7, 0x27, 0xe5, 0xcc, 0x8f, 0x27, 0x65, 0xe5, - 0xc5, 0x49, 0x39, 0xf3, 0xf3, 0x49, 0x39, 0xf3, 0x48, 0xb7, 0x98, 0x6f, 0xd6, 0x29, 0x6b, 0x84, - 0xb2, 0x0d, 0x6a, 0x7b, 0xc4, 0xb5, 0xb1, 0xd9, 0x88, 0xfe, 0x96, 0xeb, 0xe6, 0xe5, 0xa1, 0xfb, - 0xe4, 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x52, 0x5c, 0x43, 0x53, 0xa8, 0x13, 0x00, 0x00, + 0xdd, 0x8d, 0x76, 0x9b, 0x48, 0x68, 0x02, 0xa4, 0x40, 0x80, 0x22, 0xb1, 0xec, 0x26, 0x11, 0xea, + 0xd4, 0x86, 0x62, 0xa3, 0x40, 0x9a, 0x42, 0x18, 0x89, 0x63, 0x69, 0x2a, 0x92, 0xc3, 0x70, 0x48, + 0x1b, 0xc9, 0x5f, 0xe8, 0x25, 0x87, 0xfe, 0x88, 0xfe, 0x8c, 0x1e, 0x7d, 0xe8, 0x21, 0xc7, 0x9e, + 0xd8, 0xc6, 0xfe, 0x07, 0x3a, 0x15, 0x3d, 0x15, 0x33, 0x24, 0x25, 0xd2, 0x52, 0xd2, 0x28, 0x70, + 0xd3, 0x4b, 0x6e, 0xe2, 0x7b, 0xdf, 0xfb, 0xde, 0x1b, 0xbe, 0x99, 0x6f, 0x1e, 0x05, 0xcb, 0xc6, + 0x81, 0xc5, 0x0c, 0x62, 0xd6, 0x1d, 0x97, 0x79, 0x0c, 0x95, 0x0c, 0xe2, 0x1c, 0xd0, 0xe7, 0x75, + 0x69, 0x5b, 0xd3, 0xfb, 0x8c, 0xf5, 0x4d, 0xd2, 0x90, 0xbe, 0xae, 0xbf, 0xdf, 0xf0, 0xa8, 0x45, + 0xb8, 0x87, 0x2d, 0x27, 0x84, 0xaf, 0x5d, 0xef, 0x53, 0x6f, 0xe0, 0x77, 0xeb, 0x3d, 0x66, 0x35, + 0xfa, 0xac, 0xcf, 0x26, 0x48, 0xf1, 0x24, 0x1f, 0xe4, 0xaf, 0x10, 0x5e, 0xfd, 0xb1, 0x00, 0xb9, + 0xed, 0x43, 0x9b, 0xb8, 0xe8, 0x3e, 0x64, 0xa9, 0xa1, 0x29, 0x15, 0xa5, 0x56, 0x68, 0x7e, 0x72, + 0x1c, 0xe8, 0xd9, 0xd6, 0xe6, 0x28, 0xd0, 0xe1, 0xa9, 0x8f, 0x8d, 0xdb, 0xd5, 0xbb, 0xd4, 0xa8, + 0xfe, 0x16, 0xe8, 0x7a, 0x82, 0xbc, 0x87, 0x9f, 0x99, 0xe4, 0x59, 0xdf, 0xc5, 0xce, 0xa0, 0x21, + 0x40, 0xf5, 0x56, 0xbb, 0xd5, 0xce, 0x52, 0x03, 0xf5, 0x01, 0x7a, 0x2e, 0xc1, 0x1e, 0x31, 0x3a, + 0xd8, 0xd3, 0x16, 0x2a, 0x4a, 0xad, 0x78, 0x63, 0xad, 0x1e, 0xd6, 0x5d, 0x8f, 0xab, 0xa9, 0xef, + 0xc6, 0x75, 0x37, 0xaf, 0x1d, 0x05, 0xba, 0x32, 0x0a, 0xf4, 0x4a, 0x98, 0x8a, 0xf7, 0x06, 0xc4, + 0xc2, 0xb7, 0x23, 0x8a, 0x75, 0xef, 0x1a, 0x73, 0x3c, 0xca, 0x6c, 0x6c, 0x56, 0x5f, 0xfc, 0xac, + 0x2b, 0xed, 0xc2, 0xd8, 0x21, 0x12, 0xf9, 0x8e, 0x11, 0x27, 0x5a, 0x7c, 0xc7, 0x44, 0x11, 0xc5, + 0x74, 0xa2, 0xb1, 0x03, 0x3d, 0x80, 0x25, 0x93, 0xf5, 0xb0, 0xd9, 0xa1, 0x86, 0x96, 0x93, 0x2f, + 0xe8, 0xfa, 0x71, 0xa0, 0x9f, 0xdb, 0x12, 0x36, 0xf9, 0x96, 0xca, 0x29, 0x46, 0x89, 0x6d, 0x19, + 0x13, 0xbe, 0xf6, 0xb9, 0xc8, 0x84, 0x1e, 0xc2, 0xe2, 0x90, 0xda, 0x86, 0x06, 0x15, 0xa5, 0x76, + 0xfe, 0x86, 0x56, 0x4f, 0xf6, 0xb6, 0x2e, 0xfb, 0x50, 0xff, 0x9c, 0xda, 0x46, 0x53, 0x1f, 0x05, + 0xfa, 0x3f, 0x52, 0xa4, 0x22, 0x2c, 0xc1, 0x28, 0x69, 0xd0, 0x06, 0x00, 0x1f, 0x30, 0xd7, 0xeb, + 0xd8, 0xd8, 0x22, 0x5a, 0x51, 0x96, 0xf6, 0xef, 0xa9, 0x15, 0x4a, 0xc8, 0x17, 0xd8, 0x22, 0x89, + 0xf8, 0xc2, 0xd8, 0x88, 0xee, 0x42, 0x61, 0xdf, 0x37, 0xcd, 0x90, 0xa3, 0x24, 0x39, 0xae, 0x8c, + 0x02, 0x5d, 0x4f, 0x71, 0x08, 0xc4, 0x29, 0x8a, 0xa5, 0xd8, 0x86, 0xb6, 0x21, 0x6f, 0xb8, 0xf4, + 0x80, 0xb8, 0xda, 0xb2, 0x5c, 0xd7, 0xc5, 0xf4, 0xba, 0x36, 0xa5, 0xaf, 0x79, 0x79, 0x14, 0xe8, + 0xff, 0x4a, 0x91, 0x86, 0x41, 0x09, 0xca, 0x88, 0x06, 0xdd, 0x81, 0xa5, 0x01, 0xb3, 0x88, 0x83, + 0xfb, 0x44, 0x3b, 0xff, 0x9a, 0x8a, 0x62, 0x40, 0xb2, 0xa2, 0xd8, 0x86, 0x1e, 0x40, 0xd1, 0x20, + 0xbc, 0xe7, 0x52, 0xe9, 0xd3, 0x56, 0x24, 0xc7, 0x7f, 0x47, 0x81, 0x5e, 0x4d, 0x17, 0x30, 0xc1, + 0x24, 0x68, 0x92, 0xa1, 0x68, 0x1f, 0x8a, 0xfb, 0xcc, 0x1d, 0x76, 0xb8, 0x87, 0x3d, 0x9f, 0x6b, + 0xaa, 0x5c, 0x60, 0x79, 0x56, 0xe3, 0xee, 0x31, 0x77, 0xf8, 0x48, 0xa2, 0x9a, 0xff, 0x19, 0x05, + 0xfa, 0xe5, 0xf4, 0xfb, 0x1b, 0x3b, 0x13, 0x89, 0x60, 0x62, 0x45, 0x3b, 0x00, 0xf8, 0x00, 0x7b, + 0xd8, 0xed, 0xf8, 0xae, 0xa9, 0xad, 0xca, 0x82, 0x3f, 0x3e, 0x0e, 0xf4, 0xc2, 0xba, 0xb4, 0xee, + 0xb5, 0xb7, 0xa6, 0xfa, 0x1a, 0xe2, 0xf7, 0x5c, 0x33, 0xd9, 0xd7, 0xb1, 0x11, 0x3d, 0x81, 0xc2, + 0x00, 0xf3, 0x0e, 0x13, 0xc5, 0x69, 0x86, 0x24, 0xbc, 0x33, 0x0a, 0x74, 0x2d, 0xe4, 0x18, 0x60, + 0x2e, 0xcb, 0x9e, 0xc4, 0xbe, 0xcd, 0xf9, 0x5e, 0x8a, 0xc3, 0xaa, 0x7b, 0xb0, 0x28, 0x76, 0x2a, + 0x5a, 0x81, 0xe2, 0x9e, 0x3d, 0xb4, 0xd9, 0xa1, 0x2d, 0x1e, 0xd5, 0x0c, 0x5a, 0x82, 0xc5, 0x3d, + 0x4e, 0x5c, 0x55, 0x41, 0x2a, 0x94, 0xb6, 0xdd, 0x3e, 0xb6, 0xe9, 0x73, 0x2c, 0x52, 0xa8, 0x59, + 0xe1, 0xdb, 0x25, 0xd8, 0x52, 0x17, 0xc4, 0xaf, 0x36, 0x71, 0x98, 0xba, 0x88, 0x4a, 0xb0, 0xb4, + 0xe3, 0xb2, 0x03, 0x6a, 0x10, 0x57, 0xcd, 0x55, 0x3f, 0x05, 0x98, 0xbc, 0x47, 0x74, 0x09, 0x56, + 0x23, 0xf2, 0x89, 0x51, 0xcd, 0x20, 0x80, 0x7c, 0x8b, 0x0b, 0x8b, 0xaa, 0x88, 0xf0, 0x16, 0x7f, + 0xc4, 0x7c, 0xb7, 0x47, 0xd4, 0x6c, 0xf5, 0xbb, 0x8b, 0xb0, 0xb8, 0x8b, 0xf9, 0xf0, 0x83, 0x9a, + 0xbd, 0x0f, 0x35, 0xdb, 0x4a, 0xa9, 0xd9, 0xdf, 0xd2, 0x87, 0x42, 0xb4, 0x61, 0x2e, 0x31, 0xbb, + 0x05, 0x39, 0x8f, 0x7a, 0x66, 0xac, 0x63, 0x95, 0x51, 0xa0, 0xff, 0x33, 0x15, 0x25, 0xbd, 0x89, + 0xb0, 0x10, 0x7e, 0xfa, 0xac, 0x97, 0xde, 0xfd, 0xac, 0x9f, 0xb9, 0x8e, 0x7d, 0x05, 0x79, 0xc3, + 0x27, 0x1d, 0x66, 0x4b, 0x15, 0x7b, 0x73, 0x3f, 0x6b, 0x51, 0x3f, 0xd3, 0x6b, 0x36, 0x7c, 0xb2, + 0x6d, 0x9f, 0xea, 0x65, 0x4e, 0x1a, 0x91, 0x05, 0xa5, 0x1e, 0xb3, 0x1c, 0x93, 0x44, 0x5b, 0x66, + 0xe5, 0x0f, 0x53, 0xd4, 0xa3, 0x14, 0xe9, 0x17, 0x33, 0x26, 0x99, 0xda, 0x34, 0xc5, 0x84, 0x0b, + 0xed, 0x40, 0x4e, 0x68, 0x20, 0x89, 0x24, 0x50, 0x9b, 0xd1, 0x6d, 0x71, 0x40, 0xc9, 0x8c, 0xc6, + 0xc9, 0xb8, 0x64, 0xe3, 0xa4, 0x01, 0x6d, 0x42, 0x81, 0xf2, 0x8e, 0xc9, 0x7a, 0x43, 0x62, 0x48, + 0xc5, 0x5b, 0x6a, 0x5e, 0x8d, 0x2a, 0x4c, 0x4b, 0x3d, 0xe5, 0x5b, 0x12, 0x94, 0x94, 0xfa, 0xd8, + 0x86, 0x5a, 0x50, 0xb2, 0x7d, 0xab, 0xd3, 0x63, 0x96, 0x45, 0x6c, 0x8f, 0x6b, 0xa8, 0xa2, 0xd4, + 0x72, 0x33, 0xfa, 0x6f, 0xfb, 0xd6, 0x46, 0x84, 0x49, 0xf6, 0x3f, 0x61, 0x46, 0xf7, 0x40, 0x3c, + 0x76, 0x7c, 0xe7, 0x80, 0x79, 0x84, 0x6b, 0x17, 0x24, 0xd3, 0xb4, 0x96, 0xdb, 0xbe, 0xb5, 0x17, + 0x42, 0x92, 0x5a, 0x3e, 0xb1, 0xa2, 0x2d, 0x58, 0x16, 0x3c, 0x06, 0x3b, 0xb4, 0x43, 0xa6, 0x8b, + 0x92, 0xe9, 0xea, 0x28, 0xd0, 0xaf, 0x9c, 0x66, 0xda, 0x8c, 0x41, 0x09, 0xae, 0x52, 0xd2, 0x8e, + 0x9e, 0x00, 0x22, 0xdc, 0xa3, 0x96, 0x94, 0x06, 0xc3, 0x77, 0xa5, 0x98, 0x6a, 0x97, 0xc2, 0x93, + 0x3b, 0x0a, 0xf4, 0xff, 0xa5, 0x28, 0xa7, 0xa1, 0x09, 0xe2, 0xd5, 0xb1, 0x77, 0x33, 0x72, 0xa2, + 0x0e, 0x80, 0xb8, 0x25, 0xb0, 0xef, 0x0d, 0x58, 0x7c, 0x4d, 0xdc, 0x1d, 0x05, 0xfa, 0xdf, 0xc7, + 0xd7, 0xc4, 0xba, 0x74, 0xcd, 0x77, 0x4f, 0x14, 0xc6, 0x71, 0xe9, 0x6b, 0x88, 0x9c, 0xf1, 0x35, + 0x84, 0x06, 0xb0, 0x2c, 0xd8, 0x2d, 0x6a, 0x12, 0xee, 0x31, 0x9b, 0x68, 0xfb, 0x32, 0xc3, 0xc6, + 0x64, 0x0f, 0x0e, 0x30, 0x7f, 0x18, 0x7b, 0xe7, 0xcb, 0x52, 0x4a, 0x86, 0x22, 0x02, 0x25, 0xf9, + 0xa2, 0x38, 0xa7, 0x7d, 0x9b, 0x10, 0xad, 0x5f, 0x59, 0xa8, 0x15, 0x9a, 0xcd, 0x89, 0xb6, 0x89, + 0x25, 0x47, 0xce, 0xf9, 0xf2, 0x14, 0x13, 0x91, 0x71, 0x1a, 0x97, 0x1c, 0x50, 0x72, 0x48, 0x5c, + 0x6d, 0x30, 0x23, 0x4d, 0x3b, 0x72, 0xce, 0x9f, 0x26, 0x8e, 0x8c, 0xbb, 0x62, 0xe2, 0x2e, 0x31, + 0x35, 0x2a, 0x73, 0xa4, 0xbb, 0xb2, 0x25, 0x3c, 0xf3, 0x77, 0x45, 0x86, 0x21, 0x13, 0x56, 0x28, + 0xef, 0x18, 0xc4, 0x21, 0xb6, 0x41, 0xed, 0xbe, 0x10, 0xc0, 0x6f, 0x64, 0x8e, 0xcd, 0x89, 0x76, + 0x52, 0xbe, 0x19, 0xfb, 0x93, 0x0a, 0xf7, 0x36, 0x89, 0x96, 0x53, 0xb1, 0xa8, 0x0b, 0x45, 0xca, + 0x3b, 0x5d, 0x21, 0x24, 0xd4, 0xee, 0x6b, 0x43, 0x99, 0x69, 0x7d, 0x14, 0xe8, 0x6b, 0x71, 0xa6, + 0x66, 0xe4, 0x9b, 0x2f, 0x0d, 0x4c, 0x02, 0xa3, 0x15, 0xb9, 0xc4, 0x94, 0x47, 0xeb, 0x90, 0x7a, + 0x03, 0xcd, 0x9c, 0x5e, 0x51, 0x3b, 0xf4, 0x7f, 0x49, 0xbd, 0xc1, 0xdc, 0x2b, 0x4a, 0xc4, 0xa2, + 0xaf, 0x01, 0x28, 0xef, 0x38, 0xd8, 0xf5, 0x3a, 0x6c, 0x5f, 0xb3, 0x4e, 0xb7, 0x87, 0xf2, 0x1d, + 0xec, 0x7a, 0xdb, 0xfb, 0x73, 0xb6, 0x27, 0x0e, 0x43, 0x8f, 0x41, 0xb4, 0x4a, 0xf2, 0x6b, 0xf6, + 0xd9, 0x90, 0x9f, 0x1b, 0x60, 0x19, 0x57, 0xed, 0xbe, 0x6e, 0x2e, 0x2c, 0x40, 0xae, 0xc5, 0xb9, + 0x4f, 0xc2, 0xc1, 0xf0, 0x21, 0x71, 0xfb, 0xa4, 0x4d, 0x9e, 0xfa, 0x84, 0x7b, 0x6a, 0x16, 0x2d, + 0x43, 0x61, 0x7c, 0xd2, 0xc2, 0xe9, 0xf0, 0x33, 0x87, 0xf6, 0xd4, 0x45, 0x11, 0xf5, 0xc8, 0x63, + 0xee, 0x33, 0x35, 0x27, 0x8c, 0x1b, 0xd8, 0x35, 0xd4, 0x7c, 0xb5, 0x21, 0x8c, 0xe2, 0x06, 0x51, + 0xa1, 0x14, 0x25, 0x91, 0xcf, 0xe1, 0xf4, 0xb9, 0xed, 0x10, 0x5b, 0x55, 0xc4, 0x90, 0xb8, 0x61, + 0x32, 0x4e, 0x0c, 0x35, 0x5b, 0x3d, 0xca, 0x43, 0x6e, 0x97, 0x39, 0xb4, 0xf7, 0x61, 0x2e, 0xfc, + 0xcb, 0xbf, 0x72, 0x65, 0x1f, 0xde, 0xcb, 0x60, 0x38, 0x19, 0xe7, 0x4a, 0x67, 0x33, 0xce, 0xdd, + 0x82, 0x5c, 0x8f, 0x99, 0x2c, 0x1c, 0x0f, 0x67, 0x15, 0x22, 0xbd, 0xc9, 0x42, 0xa4, 0xe1, 0xf4, + 0x84, 0x7a, 0xfe, 0xdd, 0x27, 0xd4, 0x3f, 0xf7, 0x9b, 0xae, 0xfa, 0x86, 0xb3, 0x2b, 0x85, 0x5d, + 0x55, 0xaa, 0xdf, 0x2a, 0x90, 0x6b, 0x62, 0xaf, 0x37, 0x40, 0x35, 0xc8, 0x79, 0x98, 0x0f, 0xb9, + 0xa6, 0x54, 0x16, 0x6a, 0xc5, 0x1b, 0x68, 0x7a, 0x20, 0x6c, 0x87, 0x00, 0xf4, 0x11, 0xe4, 0x65, + 0xc5, 0x5c, 0xcb, 0x4a, 0xe8, 0x85, 0x19, 0x9f, 0xcf, 0xed, 0x08, 0x22, 0xc0, 0x9e, 0xd8, 0x22, + 0x5c, 0x5b, 0x98, 0x05, 0x96, 0xdb, 0xa7, 0x1d, 0x41, 0xfe, 0x7f, 0x13, 0xf2, 0x61, 0x1b, 0xd1, + 0x2a, 0x2c, 0x47, 0x35, 0x87, 0x86, 0xf0, 0x33, 0xf1, 0x3e, 0xf5, 0x1e, 0xf8, 0xdd, 0x50, 0x0d, + 0x76, 0x5d, 0x62, 0x9a, 0x4c, 0xcd, 0x36, 0x5b, 0x47, 0xaf, 0xca, 0x99, 0x5f, 0x5f, 0x95, 0x33, + 0xdf, 0x1f, 0x97, 0x33, 0x47, 0xc7, 0x65, 0xe5, 0xe5, 0x71, 0x59, 0xf9, 0xe5, 0xb8, 0xac, 0xbc, + 0x38, 0x29, 0x67, 0x7e, 0x38, 0x29, 0x2b, 0x2f, 0x4f, 0xca, 0x99, 0x9f, 0x4e, 0xca, 0x99, 0xc7, + 0xba, 0xc5, 0x7c, 0xb3, 0x4e, 0x59, 0x23, 0x2c, 0xa1, 0x41, 0x6d, 0x8f, 0xb8, 0x36, 0x36, 0x1b, + 0xd1, 0x5f, 0x74, 0xdd, 0xbc, 0x3c, 0x80, 0x37, 0x7f, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x79, 0xdd, + 0x79, 0xb7, 0xb4, 0x13, 0x00, 0x00, } func (m *Owner) Marshal() (dAtA []byte, err error) { diff --git a/internal/dvparser/target.go b/internal/dvparser/target.go index c24dfd97a..8ec4b4819 100644 --- a/internal/dvparser/target.go +++ b/internal/dvparser/target.go @@ -4,7 +4,8 @@ import "moul.io/multipmuri" func ParseTargets(args []string) ([]multipmuri.Entity, error) { targets := []multipmuri.Entity{} - defaultContext := multipmuri.NewGitHubService("") + // defaultContext := multipmuri.NewGitHubService("") + defaultContext := multipmuri.NewTrelloService() for _, arg := range args { entity, err := defaultContext.RelDecodeString(arg) if err != nil { @@ -16,6 +17,7 @@ func ParseTargets(args []string) ([]multipmuri.Entity, error) { } func ParseTarget(arg string) (multipmuri.Entity, error) { - defaultContext := multipmuri.NewGitHubService("") + // defaultContext := multipmuri.NewGitHubService("") + defaultContext := multipmuri.NewTrelloService() return defaultContext.RelDecodeString(arg) } diff --git a/internal/dvserver/api.go b/internal/dvserver/api.go index 3f723c0d0..ab1db08c4 100644 --- a/internal/dvserver/api.go +++ b/internal/dvserver/api.go @@ -138,25 +138,25 @@ func (s *service) Graph(ctx context.Context, in *Graph_Input) (*Graph_Output, er // load tasks if filters.WithFetch && gitHubToken != "" { - _, err := dvcore.PullAndSave(filters.Targets, s.h, s.schema, gitHubToken, false, s.opts.Logger) + _, err := dvcore.PullAndSave(filters.Targets, s.h, s.schema, gitHubToken, s.opts.TrelloToken, s.opts.TrelloApiKey, false, s.opts.Logger) if err != nil { return nil, fmt.Errorf("pull: %w", err) } } var tasks dvmodel.Tasks - tasks, err = dvstore.LoadTasks(s.h, s.schema, filters, s.opts.Logger) + tasks, err = dvstore.LoadTasks(s.h, s.schema, s.opts.TrelloToken, s.opts.TrelloApiKey, filters, s.opts.Logger) if err != nil { return nil, fmt.Errorf("load tasks: %w", err) } // fetch if not already in db if len(tasks) == 0 { - _, err := dvcore.PullAndSave(filters.Targets, s.h, s.schema, s.opts.GitHubToken, false, s.opts.Logger) + _, err := dvcore.PullAndSave(filters.Targets, s.h, s.schema, s.opts.GitHubToken, s.opts.TrelloToken, s.opts.TrelloApiKey, false, s.opts.Logger) if err != nil { return nil, fmt.Errorf("pull: %w", err) } - tasks, err = dvstore.LoadTasks(s.h, s.schema, filters, s.opts.Logger) + tasks, err = dvstore.LoadTasks(s.h, s.schema, s.opts.TrelloToken, s.opts.TrelloApiKey, filters, s.opts.Logger) if err != nil { return nil, fmt.Errorf("load tasks: %w", err) } diff --git a/internal/dvserver/server.go b/internal/dvserver/server.go index 08ea9a7a2..af23e1b42 100644 --- a/internal/dvserver/server.go +++ b/internal/dvserver/server.go @@ -50,6 +50,8 @@ type Opts struct { Auth string Realm string GitHubToken string + TrelloToken string + TrelloApiKey string NoAutoUpdate bool AutoUpdateTargets []multipmuri.Entity AutoUpdateInterval time.Duration @@ -275,7 +277,7 @@ func New(ctx context.Context, h *cayley.Handle, schema *schema.Config, opts Opts func (s *service) autoUpdate(targets []multipmuri.Entity) { s.opts.Logger.Debug("pull and save", zap.Any("targets", targets)) - changed, err := dvcore.PullAndSave(targets, s.h, s.schema, s.opts.GitHubToken, false, s.opts.Logger) + changed, err := dvcore.PullAndSave(targets, s.h, s.schema, s.opts.GitHubToken, s.opts.TrelloToken, s.opts.TrelloApiKey, false, s.opts.Logger) if err != nil { s.opts.Logger.Warn("pull and save", zap.Error(err)) } diff --git a/internal/dvstore/query.go b/internal/dvstore/query.go index a0bbdcb5e..86a0676d3 100644 --- a/internal/dvstore/query.go +++ b/internal/dvstore/query.go @@ -13,6 +13,7 @@ import ( "go.uber.org/zap" "moul.io/depviz/v3/internal/dvmodel" "moul.io/multipmuri" + trello "moul.io/depviz/v3/internal/trelloprovider" ) func LastUpdatedIssueInRepo(ctx context.Context, h *cayley.Handle, entity multipmuri.Entity) (time.Time, error) { // nolint:interfacer @@ -62,7 +63,7 @@ type LoadTasksFilters struct { WithFetch bool } -func LoadTasks(h *cayley.Handle, schema *schema.Config, filters LoadTasksFilters, logger *zap.Logger) (dvmodel.Tasks, error) { +func LoadTasks(h *cayley.Handle, schema *schema.Config, trelloToken string, trelloApiKey string, filters LoadTasksFilters, logger *zap.Logger) (dvmodel.Tasks, error) { if (filters.Targets == nil || len(filters.Targets) == 0) && !filters.TheWorld { return nil, fmt.Errorf("missing filter.targets") } @@ -75,13 +76,30 @@ func LoadTasks(h *cayley.Handle, schema *schema.Config, filters LoadTasksFilters paths = append(paths, path.StartPath(h)) } else { for _, target := range filters.Targets { - // FIXME: handle different target types (for now only repo) - p := path.StartPath(h, quad.IRI(target.String())). - Both(). - Has(quad.IRI("rdf:type"), quad.IRI("dv:Task")) - - // FIXME: reverse depends/blocks - paths = append(paths, p) + switch target.Provider() { + case multipmuri.GitHubProvider: + // FIXME: handle different target types (for now only repo) + p := path.StartPath(h, quad.IRI(target.String())). + Both(). + Has(quad.IRI("rdf:type"), quad.IRI("dv:Task")) + + // FIXME: reverse depends/blocks + paths = append(paths, p) + + case multipmuri.TrelloProvider: + cardsId, err := trello.GetCardsId(target.LocalID()[1:], trelloToken, trelloApiKey) + if err != nil { + return nil, fmt.Errorf("load cards id failed: %w", err) + } + for _, id := range cardsId { + p := path.StartPath(h, quad.IRI("https://trello.com/c/" + id)). + Both(). + Has(quad.IRI("rdf:type"), quad.IRI("dv:Task")) + + // FIXME: reverse depends/blocks + paths = append(paths, p) + } + } } } p := paths[0] diff --git a/internal/trelloprovider/model.go b/internal/trelloprovider/model.go new file mode 100644 index 000000000..1089ddd4c --- /dev/null +++ b/internal/trelloprovider/model.go @@ -0,0 +1,69 @@ +package trelloprovider + +import ( + "github.com/cayleygraph/quad" + "github.com/adlio/trello" + "moul.io/depviz/v3/internal/dvmodel" + "moul.io/depviz/v3/internal/dvparser" + "go.uber.org/zap" +) + +func fromCards(cards []*trello.Card, logger *zap.Logger) dvmodel.Batch { + batch := dvmodel.Batch{} + for _, card := range cards { + err := fromCard(&batch, card) + if err != nil { + logger.Warn("parse card", zap.String("url", card.URL), zap.Error(err)) + continue + } + } + return batch +} + +func fromCard(batch *dvmodel.Batch, input *trello.Card) error { + + entity, err := dvparser.ParseTarget(input.URL) + if err != nil { + return err + } + + card := dvmodel.Task{ + ID: quad.IRI(entity.String()), + LocalID: entity.LocalID(), + Description: input.Desc, + Driver: dvmodel.Driver_Trello, + CreatedAt: input.DateLastActivity, + UpdatedAt: input.DateLastActivity, + Title: input.Desc , + State: dvmodel.Task_Open, + Kind: dvmodel.Task_Card, + HasOwner: quad.IRI(entity.String()), + } + + owner := dvmodel.Owner{ + ID: quad.IRI(entity.String()), + LocalID: entity.LocalID(), + Kind: dvmodel.Owner_User, + // FullName: "bob t", + // ShortName: "bob", + Driver: dvmodel.Driver_Trello, + // Homepage: "homepage", + // AvatarURL: "avatar-url", + ForkStatus: dvmodel.Owner_UnknownForkStatus, + // Description: "description_owner", + } + + topic := dvmodel.Topic{ + ID: quad.IRI(entity.String()), + LocalID: entity.LocalID(), + Title: input.Name, + Description: input.Desc, + Kind: dvmodel.Topic_Label, + Driver: dvmodel.Driver_Trello, + } + + batch.Tasks = append(batch.Tasks, &card) + batch.Owners = append(batch.Owners, &owner) + batch.Topics = append(batch.Topics, &topic) + return nil +} \ No newline at end of file diff --git a/internal/trelloprovider/trello.go b/internal/trelloprovider/trello.go new file mode 100644 index 000000000..8b987f009 --- /dev/null +++ b/internal/trelloprovider/trello.go @@ -0,0 +1,79 @@ +package trelloprovider + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" + "time" + + "github.com/adlio/trello" + "go.uber.org/zap" + "moul.io/depviz/v3/internal/dvmodel" + "moul.io/multipmuri" +) + +type Card struct { + ShortLink string +} + +type Opts struct { + Since *time.Time `json:"since"` + Logger *zap.Logger `json:"-"` +} + +func FetchCard(ctx context.Context, entity multipmuri.Entity, token string, apikey string, boardid string, out chan<- dvmodel.Batch, opts Opts) { + client := trello.NewClient(apikey, token) + board, err := client.GetBoard(boardid) + if err != nil { + opts.Logger.Error(err.Error(), zap.Error(err)) + return + } + cards, err := board.GetCards() + if err != nil { + opts.Logger.Error(err.Error(), zap.Error(err)) + return + } + batch := fromCards(cards, opts.Logger) + out <- batch +} + +func GetCardsId(BoardId string, token string, apikey string) ([]string, error) { + url := `https://api.trello.com/1/boards/`+ BoardId + `/cards?key=` + apikey + `&token=` + token + var cardsId []string + req, err := http.NewRequest("GET", url, nil) + + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + response, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + defer response.Body.Close() + + resp, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + + var allCards []Card + err = json.Unmarshal(resp, &allCards) + if err != nil { + return nil, err + } + + var value string + for i := 0; i < len(allCards); i++ { + value = allCards[i].ShortLink + if value == "" { + break + } + cardsId = append(cardsId, value) + } + return cardsId, nil +} \ No newline at end of file diff --git a/web/src/App.js b/web/src/App.js index c830a3ba4..fc4b56207 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -21,6 +21,9 @@ const defaultTargets = process.env.DEFAULT_TARGETS const App = () => { const [showAuthModal, setShowAuthModal] = useState(false) // !store.getItem('auth_token')) const [authToken, setAuthToken] = useState(store.getItem('auth_token') || '') + const [trelloApiKey, setTrelloApiKey] = useState(store.getItem('trello_api_key') || '') + const [trelloAuthToken, setTrelloAuthToken] = useState(store.getItem('trello_auth_token') || '') + const searchParams = new URLSearchParams(window.location.search) let targets = '' if (defaultTargets) { @@ -35,7 +38,7 @@ const App = () => { layout: searchParams.get('layout') || '', } - const handleChange = (e) => { + const handleChangeGit = (e) => { e.preventDefault() const token = event.target.value || '' store.setItem('auth_token', token) @@ -43,6 +46,22 @@ const App = () => { // setShowAuthModal(!token) } + const handleChangeTrelloApi = (e) => { + e.preventDefault() + const trelloApiToken = event.target.value || '' + store.setItem('trello_api_key', trelloApiToken) + setTrelloApiKey(trelloApiToken) + // setShowAuthModal(!token) + } + + const handleChangeTrelloAuth = (e) => { + e.preventDefault() + const trelloToken = event.target.value || '' + store.setItem('trello_auth_token', trelloToken) + setTrelloAuthToken(trelloToken) + // setShowAuthModal(!token) + } + const handleClose = (e) => { e.preventDefault() setShowAuthModal(false) @@ -53,7 +72,8 @@ const App = () => {
- setShowAuthModal(true)} urlParams={urlData} /> + setShowAuthModal(true)} urlParams={urlData} /> + @@ -82,9 +102,13 @@ const App = () => {
-

Enter your auth token below.

+

Enter your auth tokens below.

- + +
+ +
+
diff --git a/web/src/ui/components/Header/Menu.js b/web/src/ui/components/Header/Menu.js index 1601949a3..6ebf05287 100644 --- a/web/src/ui/components/Header/Menu.js +++ b/web/src/ui/components/Header/Menu.js @@ -15,7 +15,7 @@ const gitHubClientId = process.env.GITHUB_CLIENT_ID const baseURL = process.env.API_URL const Menu = ({ - authToken, handleShowToken, urlParams = {}, + authToken, trelloApiKey, trelloAuthToken, handleShowToken, urlParams = {}, }) => { const { updateApiData, updateLayout, updateLoadingGraph, layout, setShowInfoBox, updateUrlData, @@ -316,13 +316,13 @@ const Menu = ({
Save as PNG - Save as JPG + Save as JPG Save as SVG (beta)
github