From acca43d0cf68612c25d5b0e643683c0401eacb42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Peso?= Date: Mon, 8 May 2023 09:22:39 +0200 Subject: [PATCH] Release 3.2.5 (#937) - https://github.com/mattermost/mattermost-plugin-jira/pull/929 - https://github.com/mattermost/mattermost-plugin-jira/pull/930 - https://github.com/mattermost/mattermost-plugin-jira/pull/935 - Revert migration to go1.18 done in one of the cherry-picked prs, go version kept as go1.13 to avoid all migration of ioutil (migrated as part of model.AppError change) - Bump 3.2.5 Fixes - Telemetry race condition - Telemetry rudder keys --- Makefile | 28 ++- build/custom.mk | 13 +- go.mod | 3 +- go.sum | 10 +- plugin.json | 4 +- server/issue.go | 1 - server/manifest.go | 2 +- server/plugin.go | 82 ++------ server/telemetry.go | 95 +++++++++ server/telemetry/logger.go | 151 ++++++++++++++ server/telemetry/rudder.go | 49 +++++ server/telemetry/tracker.go | 177 +++++++++++++++++ webapp/package-lock.json | 379 ++++++++++++------------------------ webapp/src/manifest.js | 2 +- 14 files changed, 657 insertions(+), 339 deletions(-) create mode 100644 server/telemetry.go create mode 100644 server/telemetry/logger.go create mode 100644 server/telemetry/rudder.go create mode 100644 server/telemetry/tracker.go diff --git a/Makefile b/Makefile index a648ad337..db6e28a81 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ GOPATH ?= $(shell go env GOPATH) GO_TEST_FLAGS ?= -race GO_BUILD_FLAGS ?= MM_UTILITIES_DIR ?= ../mattermost-utilities +DEFAULT_GOOS := $(shell go env GOOS) +DEFAULT_GOARCH := $(shell go env GOARCH) export GO111MODULE=on @@ -22,6 +24,12 @@ ifneq ($(wildcard build/custom.mk),) include build/custom.mk endif +ifneq ($(MM_DEBUG),) + GO_BUILD_GCFLAGS = -gcflags "all=-N -l" +else + GO_BUILD_GCFLAGS = +endif + ## Checks the code style, tests, builds and bundles the plugin. all: check-style test dist @@ -50,16 +58,24 @@ golangci-lint: @echo Running golangci-lint golangci-lint run ./... -## Builds the server, if it exists, including support for multiple architectures. +## Builds the server, if it exists, for all supported architectures, unless MM_SERVICESETTINGS_ENABLEDEVELOPER is set .PHONY: server server: ifneq ($(HAS_SERVER),) +ifneq ($(MM_DEBUG),) + $(info DEBUG mode is on; to disable, unset MM_DEBUG) +endif mkdir -p server/dist; - cd server && env GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-linux-amd64; - cd server && env GOOS=linux GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-linux-arm64; - cd server && env GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-darwin-amd64; - cd server && env GOOS=darwin GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-darwin-arm64; - cd server && env GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-windows-amd64.exe; +ifneq ($(MM_SERVICESETTINGS_ENABLEDEVELOPER),) + @echo Building plugin only for $(DEFAULT_GOOS)-$(DEFAULT_GOARCH) because MM_SERVICESETTINGS_ENABLEDEVELOPER is enabled + cd server && env CGO_ENABLED=0 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-$(DEFAULT_GOOS)-$(DEFAULT_GOARCH); +else + cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-linux-amd64; + cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-linux-arm64; + cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-darwin-amd64; + cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-darwin-arm64; + cd server && env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-windows-amd64.exe; +endif endif ## Ensures NPM dependencies are installed without having to run this all the time. diff --git a/build/custom.mk b/build/custom.mk index f0f4e030a..f75f213e5 100644 --- a/build/custom.mk +++ b/build/custom.mk @@ -1,8 +1,15 @@ # Include custom targets and environment variables here -ifndef MM_RUDDER_WRITE_KEY - MM_RUDDER_WRITE_KEY = 1d5bMvdrfWClLxgK1FvV3s4U1tg + +.DEFAULT_GOAL := all + +# If there's no MM_RUDDER_PLUGINS_PROD, add DEV data +RUDDER_WRITE_KEY = 1d5bMvdrfWClLxgK1FvV3s4U1tg +ifdef MM_RUDDER_PLUGINS_PROD + RUDDER_WRITE_KEY = $(MM_RUDDER_PLUGINS_PROD) endif -LDFLAGS += -X "github.com/mattermost/mattermost-plugin-jira/server/utils/telemetry.rudderWriteKey=$(MM_RUDDER_WRITE_KEY)" + +LDFLAGS += -X "github.com/mattermost/mattermost-plugin-jira/server/telemetry.rudderWriteKey=$(RUDDER_WRITE_KEY)" + # Build info BUILD_DATE = $(shell date -u) diff --git a/go.mod b/go.mod index e03d7b998..41e0b95f2 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,8 @@ require ( github.com/mholt/archiver/v3 v3.5.1 github.com/pkg/errors v0.9.1 github.com/rbriski/atlassian-jwt v0.0.0-20180307182949-7bb4ae273058 - github.com/stretchr/testify v1.7.0 + github.com/rudderlabs/analytics-go v3.3.2+incompatible + github.com/stretchr/testify v1.8.0 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/text v0.3.7 ) diff --git a/go.sum b/go.sum index aa0672dca..81fa18302 100644 --- a/go.sum +++ b/go.sum @@ -1549,8 +1549,9 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1558,8 +1559,10 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -2450,8 +2453,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg= gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= diff --git a/plugin.json b/plugin.json index 06156ac2b..caf6fa23f 100644 --- a/plugin.json +++ b/plugin.json @@ -4,9 +4,9 @@ "description": "Atlassian Jira plugin for Mattermost.", "homepage_url": "https://github.com/mattermost/mattermost-plugin-jira", "support_url": "https://github.com/mattermost/mattermost-plugin-jira/issues", - "release_notes_url": "https://github.com/mattermost/mattermost-plugin-jira/releases/tag/v3.2.4", + "release_notes_url": "https://github.com/mattermost/mattermost-plugin-jira/releases/tag/v3.2.5", "icon_path": "assets/icon.svg", - "version": "3.2.4", + "version": "3.2.5", "min_server_version": "6.5.0", "server": { "executables": { diff --git a/server/issue.go b/server/issue.go index 28f7c8d44..ff1697eac 100644 --- a/server/issue.go +++ b/server/issue.go @@ -539,7 +539,6 @@ func (p *Plugin) httpGetJiraProjectMetadata(w http.ResponseWriter, r *http.Reque } } - type option = utils.ReactSelectOption projects := []utils.ReactSelectOption{} issues := map[string][]utils.ReactSelectOption{} for _, prj := range plist { diff --git a/server/manifest.go b/server/manifest.go index 3aa8c9e82..187c08126 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -7,5 +7,5 @@ var manifest = struct { Version string }{ ID: "jira", - Version: "3.2.4", + Version: "3.2.5", } diff --git a/server/plugin.go b/server/plugin.go index bf88bc572..6d059de04 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -23,7 +23,6 @@ import ( pluginapi "github.com/mattermost/mattermost-plugin-api" "github.com/mattermost/mattermost-plugin-api/experimental/flow" - "github.com/mattermost/mattermost-plugin-api/experimental/telemetry" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" @@ -31,6 +30,7 @@ import ( "github.com/mattermost/mattermost-plugin-autolink/server/autolinkclient" "github.com/mattermost/mattermost-plugin-jira/server/enterprise" + "github.com/mattermost/mattermost-plugin-jira/server/telemetry" "github.com/mattermost/mattermost-plugin-jira/server/utils" ) @@ -128,15 +128,18 @@ type Plugin struct { // channel to distribute work to the webhook processors webhookQueue chan *webhookMessage + // service that determines if this Mattermost instance has access to + // enterprise features + enterpriseChecker enterprise.Checker + + // Telemetry package copied inside repository, should be changed + // to pluginapi's one (0.1.3+) when min_server_version is safe to point at 7.x + // telemetry client telemetryClient telemetry.Client // telemetry Tracker tracker telemetry.Tracker - - // service that determines if this Mattermost instance has access to - // enterprise features - enterpriseChecker enterprise.Checker } func (p *Plugin) getConfig() config { @@ -191,21 +194,10 @@ func (p *Plugin) OnConfigurationChange() error { } } - diagnostics := false - if p.API.GetConfig().LogSettings.EnableDiagnostics != nil { - diagnostics = *p.API.GetConfig().LogSettings.EnableDiagnostics - } - // create new tracker on each configuration change - p.tracker = telemetry.NewTracker( - p.telemetryClient, - p.API.GetDiagnosticId(), - p.API.GetServerVersion(), - manifest.ID, - manifest.Version, - "jira", - diagnostics, - ) + if p.tracker != nil { + p.tracker.ReloadConfig(telemetry.NewTrackerConfig(p.API.GetConfig())) + } return nil } @@ -336,11 +328,7 @@ func (p *Plugin) OnActivate() error { } }() - // initialize the rudder client once on activation - p.telemetryClient, err = telemetry.NewRudderClient() - if err != nil { - p.API.LogError("Cannot create telemetry client. err=%v", err) - } + p.initializeTelemetry() return nil } @@ -510,52 +498,6 @@ func (p *Plugin) trackWithArgs(name, userID string, args map[string]interface{}) _ = p.tracker.TrackUserEvent(name, userID, args) } -func (p *Plugin) OnSendDailyTelemetry() { - args := map[string]interface{}{} - - // Jira instances - server, cloud := 0, 0 - instances, err := p.instanceStore.LoadInstances() - if err != nil { - p.API.LogWarn("Failed to get instances for telemetry", "error", err) - } - for _, id := range instances.IDs() { - switch instances.Get(id).Type { - case ServerInstanceType: - server++ - case CloudInstanceType: - cloud++ - } - } - args["instance_count"] = server + cloud - if server > 0 { - args["server_instance_count"] = server - } - if cloud > 0 { - args["cloud_instance_count"] = cloud - } - - // Connected users - connected, err := p.userStore.CountUsers() - if err != nil { - p.API.LogWarn("Failed to get the number of connected users for telemetry", "error", err) - } - args["connected_user_count"] = connected - - // Subscriptions - subscriptions := 0 - for _, id := range instances.IDs() { - subs, err := p.getSubscriptions(id) - if err != nil { - p.API.LogWarn("Failed to get subscriptions for telemetry", "error", err) - } - subscriptions += len(subs.Channel.ByID) - } - args["subscriptions"] = subscriptions - - _ = p.tracker.TrackEvent("stats", args) -} - func (p *Plugin) OnInstall(c *plugin.Context, event model.OnInstallEvent) error { instances, err := p.instanceStore.LoadInstances() if err != nil { diff --git a/server/telemetry.go b/server/telemetry.go new file mode 100644 index 000000000..1d0e975ce --- /dev/null +++ b/server/telemetry.go @@ -0,0 +1,95 @@ +package main + +import ( + "github.com/mattermost/mattermost-plugin-jira/server/telemetry" +) + +func (p *Plugin) TrackEvent(event string, properties map[string]interface{}) { + err := p.tracker.TrackEvent(event, properties) + if err != nil { + p.API.LogDebug("Error sending telemetry event", "event", event, "error", err.Error()) + } +} + +func (p *Plugin) TrackUserEvent(event, userID string, properties map[string]interface{}) { + err := p.tracker.TrackUserEvent(event, userID, properties) + if err != nil { + p.API.LogDebug("Error sending user telemetry event", "event", event, "error", err.Error()) + } +} + +func (p *Plugin) OnSendDailyTelemetry() { + args := map[string]interface{}{} + + // Jira instances + server, cloud := 0, 0 + instances, err := p.instanceStore.LoadInstances() + if err != nil { + p.API.LogWarn("Failed to get instances for telemetry", "error", err) + } else { + for _, id := range instances.IDs() { + switch instances.Get(id).Type { + case ServerInstanceType: + server++ + case CloudInstanceType: + cloud++ + } + } + args["instance_count"] = server + cloud + if server > 0 { + args["server_instance_count"] = server + } + if cloud > 0 { + args["cloud_instance_count"] = cloud + } + + // Subscriptions + numSubscriptions := 0 + var subs *Subscriptions + for _, id := range instances.IDs() { + subs, err = p.getSubscriptions(id) + if err != nil { + p.API.LogWarn("Failed to get subscriptions for telemetry", "error", err) + } + numSubscriptions += len(subs.Channel.ByID) + } + + args["subscriptions"] = numSubscriptions + } + + // Connected users + connected, err := p.userStore.CountUsers() + if err != nil { + p.API.LogWarn("Failed to get the number of connected users for telemetry", "error", err) + } else { + args["connected_user_count"] = connected + } + + p.TrackEvent("stats", args) +} + +// Initialize telemetry setups the tracker/clients needed to send telemetry data. +// The telemetry.NewTrackerConfig(...) param will take care of extract/parse the config to set the right settings. +// If you don't want the default behavior you still can pass a different telemetry.TrackerConfig data. +func (p *Plugin) initializeTelemetry() { + var err error + + // Telemetry client + p.telemetryClient, err = telemetry.NewRudderClient() + if err != nil { + p.API.LogWarn("Telemetry client not started", "error", err.Error()) + return + } + + // Get config values + p.tracker = telemetry.NewTracker( + p.telemetryClient, + p.API.GetDiagnosticId(), + p.API.GetServerVersion(), + manifest.ID, + manifest.Version, + "jira", + telemetry.NewTrackerConfig(p.API.GetConfig()), + telemetry.NewLogger(p.API), + ) +} diff --git a/server/telemetry/logger.go b/server/telemetry/logger.go new file mode 100644 index 000000000..fa18cd979 --- /dev/null +++ b/server/telemetry/logger.go @@ -0,0 +1,151 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License for license information. + +// Copied from pluginapi/experimental/bot/logger to avoid upgrading min_server_version +// remove this file once pluginapi can be updated to 0.1.3+ ( min_server_version is +// +// safe to point at 7.x) +package telemetry + +import ( + "fmt" + "time" +) + +const ( + timed = "__since" + elapsed = "Elapsed" + + ErrorKey = "error" +) + +// LogLevel defines the level of log messages +type LogLevel string + +const ( + // LogLevelDebug denotes debug messages + LogLevelDebug = "debug" + // LogLevelInfo denotes info messages + LogLevelInfo = "info" + // LogLevelWarn denotes warn messages + LogLevelWarn = "warn" + // LogLevelError denotes error messages + LogLevelError = "error" +) + +// LogContext defines the context for the logs. +type LogContext map[string]interface{} + +// Logger defines an object able to log messages. +type Logger interface { + // With adds a logContext to the logger. + With(LogContext) Logger + // WithError adds an Error to the logger. + WithError(error) Logger + // Context returns the current context + Context() LogContext + // Timed add a timed log context. + Timed() Logger + // Debugf logs a formatted string as a debug message. + Debugf(format string, args ...interface{}) + // Errorf logs a formatted string as an error message. + Errorf(format string, args ...interface{}) + // Infof logs a formatted string as an info message. + Infof(format string, args ...interface{}) + // Warnf logs a formatted string as an warning message. + Warnf(format string, args ...interface{}) +} + +type LogAPI interface { + LogError(message string, keyValuePairs ...interface{}) + LogWarn(message string, keyValuePairs ...interface{}) + LogInfo(message string, keyValuePairs ...interface{}) + LogDebug(message string, keyValuePairs ...interface{}) +} + +type defaultLogger struct { + logContext LogContext + logAPI LogAPI +} + +func measure(lc LogContext) { + if lc[timed] == nil { + return + } + started := lc[timed].(time.Time) + lc[elapsed] = time.Since(started).String() + delete(lc, timed) +} + +func toKeyValuePairs(in map[string]interface{}) (out []interface{}) { + for k, v := range in { + out = append(out, k, v) + } + return out +} + +/* +New creates a new logger. + +- api: LogAPI implementation +*/ +func NewLogger(api LogAPI) Logger { + l := &defaultLogger{ + logAPI: api, + } + return l +} + +func (l *defaultLogger) With(logContext LogContext) Logger { + newLogger := *l + if len(newLogger.logContext) == 0 { + newLogger.logContext = map[string]interface{}{} + } + for k, v := range logContext { + newLogger.logContext[k] = v + } + return &newLogger +} + +func (l *defaultLogger) WithError(err error) Logger { + newLogger := *l + if len(newLogger.logContext) == 0 { + newLogger.logContext = map[string]interface{}{} + } + newLogger.logContext[ErrorKey] = err.Error() + return &newLogger +} + +func (l *defaultLogger) Context() LogContext { + return l.logContext +} + +func (l *defaultLogger) Timed() Logger { + return l.With(LogContext{ + timed: time.Now(), + }) +} + +func (l *defaultLogger) Debugf(format string, args ...interface{}) { + measure(l.logContext) + message := fmt.Sprintf(format, args...) + l.logAPI.LogDebug(message, toKeyValuePairs(l.logContext)...) +} + +func (l *defaultLogger) Errorf(format string, args ...interface{}) { + measure(l.logContext) + message := fmt.Sprintf(format, args...) + l.logAPI.LogError(message, toKeyValuePairs(l.logContext)...) +} + +func (l *defaultLogger) Infof(format string, args ...interface{}) { + measure(l.logContext) + message := fmt.Sprintf(format, args...) + l.logAPI.LogInfo(message, toKeyValuePairs(l.logContext)...) +} + +func (l *defaultLogger) Warnf(format string, args ...interface{}) { + measure(l.logContext) + message := fmt.Sprintf(format, args...) + l.logAPI.LogWarn(message, toKeyValuePairs(l.logContext)...) +} diff --git a/server/telemetry/rudder.go b/server/telemetry/rudder.go new file mode 100644 index 000000000..c3c3b2f8a --- /dev/null +++ b/server/telemetry/rudder.go @@ -0,0 +1,49 @@ +package telemetry + +import ( + rudder "github.com/rudderlabs/analytics-go" +) + +// rudderDataPlaneURL is set to the common Data Plane URL for all Mattermost Projects. +// It can be set during build time. More info in the package documentation. +var rudderDataPlaneURL = "https://pdat.matterlytics.com" + +// rudderWriteKey is set during build time. More info in the package documentation. +var rudderWriteKey string + +// NewRudderClient creates a new telemetry client with Rudder using the default configuration. +func NewRudderClient() (Client, error) { + return NewRudderClientWithCredentials(rudderWriteKey, rudderDataPlaneURL) +} + +// NewRudderClientWithCredentials lets you create a Rudder client with your own credentials. +func NewRudderClientWithCredentials(writeKey, dataPlaneURL string) (Client, error) { + client, err := rudder.NewWithConfig(writeKey, dataPlaneURL, rudder.Config{}) + if err != nil { + return nil, err + } + + return &rudderWrapper{client: client}, nil +} + +type rudderWrapper struct { + client rudder.Client +} + +func (r *rudderWrapper) Enqueue(t Track) error { + var context *rudder.Context + if t.InstallationID != "" { + context = &rudder.Context{Traits: map[string]interface{}{"installationId": t.InstallationID}} + } + + return r.client.Enqueue(rudder.Track{ + UserId: t.UserID, + Event: t.Event, + Context: context, + Properties: t.Properties, + }) +} + +func (r *rudderWrapper) Close() error { + return r.client.Close() +} diff --git a/server/telemetry/tracker.go b/server/telemetry/tracker.go new file mode 100644 index 000000000..3ee1d990c --- /dev/null +++ b/server/telemetry/tracker.go @@ -0,0 +1,177 @@ +package telemetry + +import ( + "os" + "sync" + + "github.com/mattermost/mattermost-server/v6/model" + "github.com/pkg/errors" +) + +type TrackerConfig struct { + EnabledTracking bool + EnabledLogging bool +} + +// NewTrackerConfig returns a new trackerConfig from the current values of the model.Config. +func NewTrackerConfig(config *model.Config) TrackerConfig { + var enabledTracking, enabledLogging bool + if config == nil { + return TrackerConfig{} + } + + if enableDiagnostics := config.LogSettings.EnableDiagnostics; enableDiagnostics != nil { + enabledTracking = *enableDiagnostics + } + + if enableDeveloper := config.ServiceSettings.EnableDeveloper; enableDeveloper != nil { + enabledLogging = *enableDeveloper + } + + return TrackerConfig{ + EnabledTracking: enabledTracking, + EnabledLogging: enabledLogging, + } +} + +// Tracker defines a telemetry tracker +type Tracker interface { + // TrackEvent registers an event through the configured telemetry client + TrackEvent(event string, properties map[string]interface{}) error + // TrackUserEvent registers an event through the configured telemetry client associated to a user + TrackUserEvent(event string, userID string, properties map[string]interface{}) error + // Reload Config re-evaluates tracker config to determine if tracking behavior should change + ReloadConfig(config TrackerConfig) +} + +// Client defines a telemetry client +type Client interface { + // Enqueue adds a tracker event (Track) to be registered + Enqueue(t Track) error + // Close closes the client connection, flushing any event left on the queue + Close() error +} + +// Track defines an event ready for the client to process +type Track struct { + UserID string + Event string + Properties map[string]interface{} + InstallationID string +} + +type tracker struct { + client Client + diagnosticID string + serverVersion string + pluginID string + pluginVersion string + telemetryShortName string + configLock sync.RWMutex + config TrackerConfig + logger Logger +} + +// NewTracker creates a default Tracker +// - c Client: A telemetry client. If nil, the tracker will not track any event. +// - diagnosticID: Server unique ID used for telemetry. +// - severVersion: Mattermost server version. +// - pluginID: The plugin ID. +// - pluginVersion: The plugin version. +// - telemetryShortName: Short name for the plugin to use in telemetry. Used to avoid dot separated names like `com.company.pluginName`. +// If a empty string is provided, it will use the pluginID. +// - config: Whether the system has enabled sending telemetry data. If false, the tracker will not track any event. +// - l Logger: A logger to debug event tracking and some important changes (it won't log if nil is passed as logger). +func NewTracker( + c Client, + diagnosticID, + serverVersion, + pluginID, + pluginVersion, + telemetryShortName string, + config TrackerConfig, + l Logger, +) Tracker { + if telemetryShortName == "" { + telemetryShortName = pluginID + } + return &tracker{ + telemetryShortName: telemetryShortName, + client: c, + diagnosticID: diagnosticID, + serverVersion: serverVersion, + pluginID: pluginID, + pluginVersion: pluginVersion, + logger: l, + config: config, + } +} + +func (t *tracker) ReloadConfig(config TrackerConfig) { + t.configLock.Lock() + defer t.configLock.Unlock() + + if config.EnabledTracking != t.config.EnabledTracking { + if config.EnabledTracking { + t.debugf("Enabling plugin telemetry") + } else { + t.debugf("Disabling plugin telemetry") + } + } + + t.config.EnabledTracking = config.EnabledTracking + t.config.EnabledLogging = config.EnabledLogging +} + +// Note that config lock is handled by the caller. +func (t *tracker) debugf(message string, args ...interface{}) { + if t.logger == nil || !t.config.EnabledLogging { + return + } + t.logger.Debugf(message, args...) +} + +func (t *tracker) TrackEvent(event string, properties map[string]interface{}) error { + t.configLock.RLock() + defer t.configLock.RUnlock() + + event = t.telemetryShortName + "_" + event + if !t.config.EnabledTracking || t.client == nil { + t.debugf("Plugin telemetry event `%s` tracked, but not sent due to configuration", event) + return nil + } + + if properties == nil { + properties = map[string]interface{}{} + } + properties["PluginID"] = t.pluginID + properties["PluginVersion"] = t.pluginVersion + properties["ServerVersion"] = t.serverVersion + + // if we are part of a cloud installation, add it's ID to the tracked event's context. + installationID := os.Getenv("MM_CLOUD_INSTALLATION_ID") + + err := t.client.Enqueue(Track{ + // We consider the server the "user" on the telemetry system. Any reference to the actual user is passed by properties. + UserID: t.diagnosticID, + Event: event, + Properties: properties, + InstallationID: installationID, + }) + + if err != nil { + return errors.Wrap(err, "cannot enqueue the track") + } + t.debugf("Tracked plugin telemetry event `%s`", event) + + return nil +} + +func (t *tracker) TrackUserEvent(event, userID string, properties map[string]interface{}) error { + if properties == nil { + properties = map[string]interface{}{} + } + + properties["UserActualID"] = userID + return t.TrackEvent(event, properties) +} diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 5683d7544..56ee263eb 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -2625,7 +2625,7 @@ "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", "dev": true }, "ansi-escapes": { @@ -2677,9 +2677,9 @@ "dev": true }, "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", "dev": true, "requires": { "delegates": "^1.0.0", @@ -2733,7 +2733,7 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", "dev": true }, "array-includes": { @@ -2896,7 +2896,7 @@ "async-foreach": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", "dev": true }, "async-limiter": { @@ -3727,7 +3727,7 @@ "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "integrity": "sha512-OorbnJVPII4DuUKbjARAe8u8EfqOmkEEaSFIyoQ7OjTHn6kafxWl0wLgoZ2rXaYd7MyLcDaU4TmhfxtwgcccMQ==", "dev": true, "requires": { "inherits": "~2.0.0" @@ -4007,7 +4007,7 @@ "camelcase-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", "dev": true, "requires": { "camelcase": "^2.0.0", @@ -4017,7 +4017,7 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", "dev": true } } @@ -4277,7 +4277,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", "dev": true }, "collection-visit": { @@ -4363,7 +4363,7 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, "constants-browserify": { @@ -4723,7 +4723,7 @@ "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", "dev": true, "requires": { "array-find-index": "^1.0.1" @@ -4870,7 +4870,7 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, "des.js": { @@ -6290,7 +6290,7 @@ "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", "dev": true, "requires": { "aproba": "^1.0.3", @@ -6306,13 +6306,13 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -6321,7 +6321,7 @@ "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -6332,7 +6332,7 @@ "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -6363,7 +6363,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", "dev": true }, "get-stream": { @@ -6484,14 +6484,22 @@ "dev": true }, "globule": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz", - "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", + "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", "dev": true, "requires": { "glob": "~7.1.1", - "lodash": "~4.17.12", + "lodash": "^4.17.21", "minimatch": "~3.0.2" + }, + "dependencies": { + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + } } }, "graceful-fs": { @@ -6606,7 +6614,7 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true }, "has-value": { @@ -6925,7 +6933,7 @@ "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "integrity": "sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==", "dev": true, "requires": { "repeating": "^2.0.0" @@ -7327,7 +7335,7 @@ "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", "dev": true }, "is-windows": { @@ -9195,9 +9203,9 @@ "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, "js-base64": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz", - "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "dev": true }, "js-levenshtein": { @@ -9569,7 +9577,7 @@ "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", "dev": true, "requires": { "currently-unhandled": "^0.4.1", @@ -9642,7 +9650,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "dev": true }, "map-visit": { @@ -10123,7 +10131,7 @@ "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "integrity": "sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==", "dev": true, "requires": { "camelcase-keys": "^2.0.0", @@ -10141,7 +10149,7 @@ "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", "dev": true, "requires": { "path-exists": "^2.0.0", @@ -10151,7 +10159,7 @@ "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -10162,15 +10170,15 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", "dev": true, "requires": { "pinkie-promise": "^2.0.0" @@ -10179,7 +10187,7 @@ "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -10190,7 +10198,7 @@ "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", "dev": true, "requires": { "load-json-file": "^1.0.0", @@ -10201,7 +10209,7 @@ "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", "dev": true, "requires": { "find-up": "^1.0.0", @@ -10211,7 +10219,7 @@ "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", "dev": true, "requires": { "is-utf8": "^0.2.0" @@ -10548,7 +10556,7 @@ "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", "dev": true, "requires": { "abbrev": "1" @@ -10557,7 +10565,7 @@ "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==", "dev": true } } @@ -10643,9 +10651,9 @@ } }, "node-sass": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz", - "integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", + "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -10662,7 +10670,7 @@ "node-gyp": "^3.8.0", "npmlog": "^4.0.0", "request": "^2.88.0", - "sass-graph": "^2.2.4", + "sass-graph": "2.2.5", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" }, @@ -10670,19 +10678,19 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", "dev": true }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", "dev": true, "requires": { "ansi-styles": "^2.2.1", @@ -10695,7 +10703,7 @@ "cross-spawn": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "integrity": "sha512-eZ+m1WNhSZutOa/uRblAc9Ut5MQfukFrFMtPSm3bZCA888NmMd5AWXWdgRZ80zd+pTk1P2JrGjg9pUPTvl2PWQ==", "dev": true, "requires": { "lru-cache": "^4.0.1", @@ -10715,7 +10723,7 @@ "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -10724,13 +10732,13 @@ "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", "dev": true }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "dev": true } } @@ -10794,7 +10802,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", "dev": true }, "nwsapi": { @@ -11020,7 +11028,7 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", "dev": true }, "os-locale": { @@ -11301,13 +11309,13 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", "dev": true }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", "dev": true, "requires": { "pinkie": "^2.0.0" @@ -11562,7 +11570,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", "dev": true }, "psl": { @@ -12119,7 +12127,7 @@ "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "integrity": "sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==", "dev": true, "requires": { "indent-string": "^2.1.0", @@ -12333,7 +12341,7 @@ "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", "dev": true, "requires": { "is-finite": "^1.0.0" @@ -12606,228 +12614,97 @@ } }, "sass-graph": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", - "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", + "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==", "dev": true, "requires": { "glob": "^7.0.0", "lodash": "^4.0.0", "scss-tokenizer": "^0.2.3", - "yargs": "^7.0.0" + "yargs": "^13.3.2" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "locate-path": "^3.0.0" } }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "p-try": "^2.0.0" } }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "p-limit": "^2.0.0" } }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" } }, "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { - "camelcase": "^3.0.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -12908,7 +12785,7 @@ "scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", - "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "integrity": "sha512-dYE8LhncfBUar6POCxMTm0Ln+erjeczqEvCJib5/7XNkdw1FkUGgwMPY360FY0FgPWQxHWCx29Jl3oejyGLM9Q==", "dev": true, "requires": { "js-base64": "^2.1.8", @@ -12918,7 +12795,7 @@ "source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==", "dev": true, "requires": { "amdefine": ">=0.0.4" @@ -13525,7 +13402,7 @@ "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==", "dev": true, "requires": { "get-stdin": "^4.0.1" @@ -14123,7 +14000,7 @@ "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "integrity": "sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==", "dev": true }, "trim-right": { @@ -14716,12 +14593,12 @@ "dev": true }, "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "dev": true, "requires": { - "string-width": "^1.0.2 || 2" + "string-width": "^1.0.2 || 2 || 3 || 4" } }, "wordwrap": { diff --git a/webapp/src/manifest.js b/webapp/src/manifest.js index 7117c8ec0..b50050ad2 100644 --- a/webapp/src/manifest.js +++ b/webapp/src/manifest.js @@ -1,4 +1,4 @@ // This file is automatically generated. Do not modify it manually. export const id = 'jira'; -export const version = '3.2.4'; +export const version = '3.2.5';