Merge remote-tracking branch 'origin/dev' into api-v1.7.0

This commit is contained in:
Taha Yassine Kraiem 2022-06-24 20:25:01 +02:00
commit 20aaff933e
317 changed files with 3342 additions and 29475 deletions

View file

@ -6,6 +6,7 @@ on:
- dev
paths:
- ee/api/**
- api/**
name: Build and Deploy Chalice EE

View file

@ -3,9 +3,13 @@ on:
workflow_dispatch:
push:
branches:
- api-v1.5.5
- dev
paths:
- frontend/**
# Disable previous workflows for this action.
concurrency:
group: ${{ github.workflow }} #-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
@ -23,6 +27,10 @@ jobs:
${{ runner.OS }}-build-
${{ runner.OS }}-
- name: Docker login
run: |
docker login ${{ secrets.EE_REGISTRY_URL }} -u ${{ secrets.EE_DOCKER_USERNAME }} -p "${{ secrets.EE_REGISTRY_TOKEN }}"
- uses: azure/k8s-set-context@v1
with:
method: kubeconfig
@ -31,16 +39,60 @@ jobs:
# - name: Install
# run: npm install
- name: Build and deploy
- name: Building and Pushing frontend image
id: build-image
env:
DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }}
IMAGE_TAG: ${{ github.sha }}
ENVIRONMENT: staging
run: |
cd frontend
bash build.sh
cp -arl public frontend
minio_pod=$(kubectl get po -n db -l app.kubernetes.io/name=minio -n db --output custom-columns=name:.metadata.name | tail -n+2)
echo $minio_pod
echo copying frontend to container.
kubectl -n db cp frontend $minio_pod:/data/
rm -rf frontend
mv .env.sample .env
docker run --rm -v /etc/passwd:/etc/passwd -u `id -u`:`id -g` -v $(pwd):/home/${USER} -w /home/${USER} --name node_build node:14-stretch-slim /bin/bash -c "yarn && yarn build"
# https://github.com/docker/cli/issues/1134#issuecomment-613516912
DOCKER_BUILDKIT=1 docker build --target=cicd -t $DOCKER_REPO/frontend:${IMAGE_TAG} .
docker push $DOCKER_REPO/frontend:${IMAGE_TAG}
- name: Creating old image input
run: |
#
# Create yaml with existing image tags
#
kubectl get pods -n app -o jsonpath="{.items[*].spec.containers[*].image}" |\
tr -s '[[:space:]]' '\n' | sort | uniq -c | grep '/foss/' | cut -d '/' -f3 > /tmp/image_tag.txt
echo > /tmp/image_override.yaml
for line in `cat /tmp/image_tag.txt`;
do
image_array=($(echo "$line" | tr ':' '\n'))
cat <<EOF >> /tmp/image_override.yaml
${image_array[0]}:
image:
tag: ${image_array[1]}
EOF
done
- name: Deploy to kubernetes
run: |
cd scripts/helmcharts/
## Update secerts
sed -i "s/postgresqlPassword: \"changeMePassword\"/postgresqlPassword: \"${{ secrets.OSS_PG_PASSWORD }}\"/g" vars.yaml
sed -i "s/accessKey: \"changeMeMinioAccessKey\"/accessKey: \"${{ secrets.OSS_MINIO_ACCESS_KEY }}\"/g" vars.yaml
sed -i "s/secretKey: \"changeMeMinioPassword\"/secretKey: \"${{ secrets.OSS_MINIO_SECRET_KEY }}\"/g" vars.yaml
sed -i "s/jwt_secret: \"SetARandomStringHere\"/jwt_secret: \"${{ secrets.OSS_JWT_SECRET }}\"/g" vars.yaml
sed -i "s/domainName: \"\"/domainName: \"${{ secrets.OSS_DOMAIN_NAME }}\"/g" vars.yaml
# Update changed image tag
sed -i "/frontend/{n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml
cat /tmp/image_override.yaml
# Deploy command
helm upgrade --install openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --atomic
env:
DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }}
IMAGE_TAG: ${{ github.sha }}
ENVIRONMENT: staging
# - name: Debug Job
# if: ${{ failure() }}

View file

@ -7,6 +7,7 @@ on:
- dev
paths:
- ee/backend/**
- backend/**
name: Build and deploy workers EE
@ -118,7 +119,7 @@ jobs:
## Update images
for image in $(cat /tmp/images_to_build.txt);
do
sed -i "/${image}/{n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml
sed -i "/${image}/{n;n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml
done
cat /tmp/image_override.yaml

View file

@ -33,11 +33,12 @@ jobs:
method: kubeconfig
kubeconfig: ${{ secrets.OSS_KUBECONFIG }} # Use content of kubeconfig in secret.
id: setcontext
# Caching docker images
- uses: satackey/action-docker-layer-caching@v0.0.11
# Ignore the failure of a step and avoid terminating the job.
continue-on-error: true
# - uses: satackey/action-docker-layer-caching@v0.0.11
# # Ignore the failure of a step and avoid terminating the job.
# continue-on-error: true
- name: Build, tag
id: build-image

View file

@ -6,13 +6,14 @@ WORKDIR /root
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go mod tidy && go mod download
FROM prepare AS build
COPY cmd cmd
COPY pkg pkg
COPY internal internal
RUN go mod tidy
ARG SERVICE_NAME
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o service -tags musl openreplay/backend/cmd/$SERVICE_NAME
@ -27,7 +28,7 @@ ENV TZ=UTC \
MAXMINDDB_FILE=/root/geoip.mmdb \
UAPARSER_FILE=/root/regexes.yaml \
HTTP_PORT=80 \
BEACON_SIZE_LIMIT=7000000 \
BEACON_SIZE_LIMIT=1000000 \
KAFKA_USE_SSL=true \
KAFKA_MAX_POLL_INTERVAL_MS=400000 \
REDIS_STREAMS_MAX_LEN=10000 \
@ -50,8 +51,8 @@ ENV TZ=UTC \
FS_CLEAN_HRS=72 \
FILE_SPLIT_SIZE=300000 \
LOG_QUEUE_STATS_INTERVAL_SEC=60 \
BATCH_QUEUE_LIMIT=20 \
BATCH_SIZE_LIMIT=10000000 \
DB_BATCH_QUEUE_LIMIT=20 \
DB_BATCH_SIZE_LIMIT=10000000 \
PARTITIONS_NUMBER=1

View file

@ -32,7 +32,7 @@ func main() {
cfg := db.New()
// Init database
pg := cache.NewPGCache(postgres.NewConn(cfg.Postgres), cfg.ProjectExpirationTimeoutMs)
pg := cache.NewPGCache(postgres.NewConn(cfg.Postgres, cfg.BatchQueueLimit, cfg.BatchSizeLimit), cfg.ProjectExpirationTimeoutMs)
defer pg.Close()
// HandlersFabric returns the list of message handlers we want to be applied to each incoming message.

View file

@ -4,6 +4,8 @@ import (
"log"
"openreplay/backend/internal/config/ender"
"openreplay/backend/internal/sessionender"
"openreplay/backend/pkg/db/cache"
"openreplay/backend/pkg/db/postgres"
"openreplay/backend/pkg/monitoring"
"time"
@ -30,6 +32,9 @@ func main() {
// Load service configuration
cfg := ender.New()
pg := cache.NewPGCache(postgres.NewConn(cfg.Postgres, 0, 0), cfg.ProjectExpirationTimeoutMs)
defer pg.Close()
// Init all modules
statsLogger := logger.NewQueueStats(cfg.LoggerTimeout)
sessions, err := sessionender.New(metrics, intervals.EVENTS_SESSION_END_TIMEOUT, cfg.PartitionsNumber)
@ -44,8 +49,17 @@ func main() {
cfg.TopicRawWeb,
},
func(sessionID uint64, msg messages.Message, meta *types.Meta) {
switch msg.(type) {
case *messages.SessionStart, *messages.SessionEnd:
// Skip several message types
return
}
// Test debug
if msg.Meta().Timestamp == 0 {
log.Printf("ZERO TS, sessID: %d, msgType: %d", sessionID, msg.TypeID())
}
statsLogger.Collect(sessionID, meta)
sessions.UpdateSession(sessionID, meta.Timestamp)
sessions.UpdateSession(sessionID, meta.Timestamp, msg.Meta().Timestamp)
},
false,
)
@ -70,8 +84,12 @@ func main() {
// Find ended sessions and send notification to other services
sessions.HandleEndedSessions(func(sessionID uint64, timestamp int64) bool {
msg := &messages.SessionEnd{Timestamp: uint64(timestamp)}
if err := pg.InsertSessionEnd(sessionID, msg.Timestamp); err != nil {
log.Printf("can't save sessionEnd to database, sessID: %d", sessionID)
return false
}
if err := producer.Produce(cfg.TopicRawWeb, sessionID, messages.Encode(msg)); err != nil {
log.Printf("can't send SessionEnd to trigger topic: %s; sessID: %d", err, sessionID)
log.Printf("can't send sessionEnd to topic: %s; sessID: %d", err, sessionID)
return false
}
return true

View file

@ -4,8 +4,6 @@ import (
"log"
"openreplay/backend/internal/config/heuristics"
"openreplay/backend/pkg/handlers"
"openreplay/backend/pkg/handlers/custom"
ios2 "openreplay/backend/pkg/handlers/ios"
web2 "openreplay/backend/pkg/handlers/web"
"openreplay/backend/pkg/intervals"
logger "openreplay/backend/pkg/log"
@ -39,12 +37,12 @@ func main() {
&web2.MemoryIssueDetector{},
&web2.NetworkIssueDetector{},
&web2.PerformanceAggregator{},
// iOS handlers
&ios2.AppNotResponding{},
&ios2.ClickRageDetector{},
&ios2.PerformanceAggregator{},
// iOS's handlers
//&ios2.AppNotResponding{},
//&ios2.ClickRageDetector{},
//&ios2.PerformanceAggregator{},
// Other handlers (you can add your custom handlers here)
&custom.CustomHandler{},
//&custom.CustomHandler{},
}
}

View file

@ -35,7 +35,7 @@ func main() {
defer producer.Close(15000)
// Connect to database
dbConn := cache.NewPGCache(postgres.NewConn(cfg.Postgres), 1000*60*20)
dbConn := cache.NewPGCache(postgres.NewConn(cfg.Postgres, 0, 0), 1000*60*20)
defer dbConn.Close()
// Build all services

View file

@ -26,7 +26,7 @@ func main() {
cfg := config.New()
pg := postgres.NewConn(cfg.PostgresURI)
pg := postgres.NewConn(cfg.PostgresURI, 0, 0)
defer pg.Close()
tokenizer := token.NewTokenizer(cfg.TokenSecret)
@ -74,7 +74,7 @@ func main() {
log.Printf("Requesting all...\n")
manager.RequestAll()
case event := <-manager.Events:
log.Printf("New integration event: %+v\n", *event.RawErrorEvent)
log.Printf("New integration event: %+v\n", *event.IntegrationEvent)
sessionID := event.SessionID
if sessionID == 0 {
sessData, err := tokenizer.Parse(event.Token)
@ -84,8 +84,7 @@ func main() {
}
sessionID = sessData.ID
}
// TODO: send to ready-events topic. Otherwise it have to go through the events worker.
producer.Produce(cfg.TopicRawWeb, sessionID, messages.Encode(event.RawErrorEvent))
producer.Produce(cfg.TopicAnalytics, sessionID, messages.Encode(event.IntegrationEvent))
case err := <-manager.Errors:
log.Printf("Integration error: %v\n", err)
case i := <-manager.RequestDataUpdates:

View file

@ -26,7 +26,7 @@ func New() *Config {
TopicRawWeb: env.String("TOPIC_RAW_WEB"),
TopicAnalytics: env.String("TOPIC_ANALYTICS"),
CommitBatchTimeout: 15 * time.Second,
BatchQueueLimit: env.Int("BATCH_QUEUE_LIMIT"),
BatchSizeLimit: env.Int("BATCH_SIZE_LIMIT"),
BatchQueueLimit: env.Int("DB_BATCH_QUEUE_LIMIT"),
BatchSizeLimit: env.Int("DB_BATCH_SIZE_LIMIT"),
}
}

View file

@ -5,19 +5,23 @@ import (
)
type Config struct {
GroupEnder string
LoggerTimeout int
TopicRawWeb string
ProducerTimeout int
PartitionsNumber int
Postgres string
ProjectExpirationTimeoutMs int64
GroupEnder string
LoggerTimeout int
TopicRawWeb string
ProducerTimeout int
PartitionsNumber int
}
func New() *Config {
return &Config{
GroupEnder: env.String("GROUP_ENDER"),
LoggerTimeout: env.Int("LOG_QUEUE_STATS_INTERVAL_SEC"),
TopicRawWeb: env.String("TOPIC_RAW_WEB"),
ProducerTimeout: 2000,
PartitionsNumber: env.Int("PARTITIONS_NUMBER"),
Postgres: env.String("POSTGRES_STRING"),
ProjectExpirationTimeoutMs: 1000 * 60 * 20,
GroupEnder: env.String("GROUP_ENDER"),
LoggerTimeout: env.Int("LOG_QUEUE_STATS_INTERVAL_SEC"),
TopicRawWeb: env.String("TOPIC_RAW_WEB"),
ProducerTimeout: 2000,
PartitionsNumber: env.Int("PARTITIONS_NUMBER"),
}
}

View file

@ -10,8 +10,8 @@ type Config struct {
func New() *Config {
return &Config{
TopicRawWeb: env.String("TOPIC_RAW_WEB"),
PostgresURI: env.String("POSTGRES_STRING"),
TokenSecret: env.String("TOKEN_SECRET"),
TopicAnalytics: env.String("TOPIC_ANALYTICS"),
PostgresURI: env.String("POSTGRES_STRING"),
TokenSecret: env.String("TOKEN_SECRET"),
}
}

View file

@ -19,9 +19,9 @@ func (mi *Saver) InsertMessage(sessionID uint64, msg Message) error {
// Web
case *SessionStart:
return mi.pg.InsertWebSessionStart(sessionID, m)
return mi.pg.HandleWebSessionStart(sessionID, m)
case *SessionEnd:
return mi.pg.InsertWebSessionEnd(sessionID, m)
return mi.pg.HandleWebSessionEnd(sessionID, m)
case *UserID:
return mi.pg.InsertWebUserID(sessionID, m)
case *UserAnonymousID:
@ -42,6 +42,15 @@ func (mi *Saver) InsertMessage(sessionID uint64, msg Message) error {
return mi.pg.InsertWebFetchEvent(sessionID, m)
case *GraphQLEvent:
return mi.pg.InsertWebGraphQLEvent(sessionID, m)
case *IntegrationEvent:
return mi.pg.InsertWebErrorEvent(sessionID, &ErrorEvent{
MessageID: m.Meta().Index,
Timestamp: m.Timestamp,
Source: m.Source,
Name: m.Name,
Message: m.Message,
Payload: m.Payload,
})
// IOS
case *IOSSessionStart:
@ -66,15 +75,6 @@ func (mi *Saver) InsertMessage(sessionID uint64, msg Message) error {
case *IOSCrash:
return mi.pg.InsertIOSCrash(sessionID, m)
case *RawErrorEvent:
return mi.pg.InsertWebErrorEvent(sessionID, &ErrorEvent{
MessageID: m.Meta().Index, // TODO: is it possible to catch panic here???
Timestamp: m.Timestamp,
Source: m.Source,
Name: m.Name,
Message: m.Message,
Payload: m.Payload,
})
}
return nil // "Not implemented"
}

View file

@ -98,7 +98,7 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request)
expTime := startTime.Add(time.Duration(p.MaxSessionDuration) * time.Millisecond)
tokenData = &token.TokenData{ID: sessionID, ExpTime: expTime.UnixMilli()}
e.services.Producer.Produce(e.cfg.TopicRawWeb, tokenData.ID, Encode(&SessionStart{
sessionStart := &SessionStart{
Timestamp: req.Timestamp,
ProjectID: uint64(p.ProjectID),
TrackerVersion: req.TrackerVersion,
@ -115,7 +115,13 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request)
UserDeviceMemorySize: req.DeviceMemory,
UserDeviceHeapSize: req.JsHeapSizeLimit,
UserID: req.UserID,
}))
}
// Save sessionStart to db
e.services.Database.InsertWebSessionStart(sessionID, sessionStart)
// Send sessionStart message to kafka
e.services.Producer.Produce(e.cfg.TopicRawWeb, tokenData.ID, Encode(sessionStart))
}
ResponseWithJSON(w, &StartSessionResponse{

View file

@ -97,7 +97,7 @@ func (b *bugsnag) Request(c *client) error {
c.evChan <- &SessionErrorEvent{
SessionID: sessionID,
Token: token,
RawErrorEvent: &messages.RawErrorEvent{
IntegrationEvent: &messages.IntegrationEvent{
Source: "bugsnag",
Timestamp: timestamp,
Name: e.Exceptions[0].Message,

View file

@ -40,7 +40,7 @@ type client struct {
type SessionErrorEvent struct {
SessionID uint64
Token string
*messages.RawErrorEvent
*messages.IntegrationEvent
}
type ClientMap map[string]*client

View file

@ -68,7 +68,7 @@ func (cw *cloudwatch) Request(c *client) error {
c.evChan <- &SessionErrorEvent{
//SessionID: sessionID,
Token: token,
RawErrorEvent: &messages.RawErrorEvent{
IntegrationEvent: &messages.IntegrationEvent{
Source: "cloudwatch",
Timestamp: timestamp, // e.IngestionTime ??
Name: name,

View file

@ -115,7 +115,7 @@ func (d *datadog) Request(c *client) error {
c.evChan <- &SessionErrorEvent{
//SessionID: sessionID,
Token: token,
RawErrorEvent: &messages.RawErrorEvent{
IntegrationEvent: &messages.IntegrationEvent{
Source: "datadog",
Timestamp: timestamp,
Name: ddLog.Content.Attributes.Error.Message,

View file

@ -181,7 +181,7 @@ func (es *elasticsearch) Request(c *client) error {
//SessionID: sessionID,
SessionID: sessionID,
Token: token,
RawErrorEvent: &messages.RawErrorEvent{
IntegrationEvent: &messages.IntegrationEvent{
Source: "elasticsearch",
Timestamp: timestamp,
Name: fmt.Sprintf("%v", docID),

View file

@ -89,7 +89,7 @@ func (nr *newrelic) Request(c *client) error {
c.setLastMessageTimestamp(e.Timestamp)
c.evChan <- &SessionErrorEvent{
Token: e.OpenReplaySessionToken,
RawErrorEvent: &messages.RawErrorEvent{
IntegrationEvent: &messages.IntegrationEvent{
Source: "newrelic",
Timestamp: e.Timestamp,
Name: e.ErrorClass,

View file

@ -156,7 +156,7 @@ func (rb *rollbar) Request(c *client) error {
c.setLastMessageTimestamp(timestamp)
c.evChan <- &SessionErrorEvent{
Token: e["body.message.openReplaySessionToken"],
RawErrorEvent: &messages.RawErrorEvent{
IntegrationEvent: &messages.IntegrationEvent{
Source: "rollbar",
Timestamp: timestamp,
Name: e["item.title"],

View file

@ -115,7 +115,7 @@ PageLoop:
c.evChan <- &SessionErrorEvent{
SessionID: sessionID,
Token: token,
RawErrorEvent: &messages.RawErrorEvent{
IntegrationEvent: &messages.IntegrationEvent{
Source: "sentry",
Timestamp: timestamp,
Name: e.Title,

View file

@ -89,7 +89,7 @@ func (sd *stackdriver) Request(c *client) error {
c.evChan <- &SessionErrorEvent{
//SessionID: sessionID,
Token: token,
RawErrorEvent: &messages.RawErrorEvent{
IntegrationEvent: &messages.IntegrationEvent{
Source: "stackdriver",
Timestamp: timestamp,
Name: e.InsertID, // not sure about that

View file

@ -193,7 +193,7 @@ func (sl *sumologic) Request(c *client) error {
c.evChan <- &SessionErrorEvent{
//SessionID: sessionID,
Token: token,
RawErrorEvent: &messages.RawErrorEvent{
IntegrationEvent: &messages.IntegrationEvent{
Source: "sumologic",
Timestamp: e.Timestamp,
Name: name,

View file

@ -16,6 +16,7 @@ type EndedSessionHandler func(sessionID uint64, timestamp int64) bool
type session struct {
lastTimestamp int64
lastUpdate int64
lastUserTime int64
isEnded bool
}
@ -51,7 +52,7 @@ func New(metrics *monitoring.Metrics, timeout int64, parts int) (*SessionEnder,
}
// UpdateSession save timestamp for new sessions and update for existing sessions
func (se *SessionEnder) UpdateSession(sessionID uint64, timestamp int64) {
func (se *SessionEnder) UpdateSession(sessionID uint64, timestamp, msgTimestamp int64) {
localTS := time.Now().UnixMilli()
currTS := timestamp
if currTS == 0 {
@ -62,11 +63,12 @@ func (se *SessionEnder) UpdateSession(sessionID uint64, timestamp int64) {
sess, ok := se.sessions[sessionID]
if !ok {
se.sessions[sessionID] = &session{
lastTimestamp: currTS, // timestamp from message broker
lastUpdate: localTS, // local timestamp
lastTimestamp: currTS, // timestamp from message broker
lastUpdate: localTS, // local timestamp
lastUserTime: msgTimestamp, // last timestamp from user's machine
isEnded: false,
}
log.Printf("added new session: %d", sessionID)
//log.Printf("added new session: %d", sessionID)
se.activeSessions.Add(context.Background(), 1)
se.totalSessions.Add(context.Background(), 1)
return
@ -74,6 +76,7 @@ func (se *SessionEnder) UpdateSession(sessionID uint64, timestamp int64) {
if currTS > sess.lastTimestamp {
sess.lastTimestamp = currTS
sess.lastUpdate = localTS
sess.lastUserTime = msgTimestamp
sess.isEnded = false
}
}
@ -86,10 +89,12 @@ func (se *SessionEnder) HandleEndedSessions(handler EndedSessionHandler) {
if sess.isEnded || (se.timeCtrl.LastTimestamp(sessID)-sess.lastTimestamp > se.timeout) ||
(currTime-sess.lastUpdate > se.timeout) {
sess.isEnded = true
if handler(sessID, sess.lastTimestamp) {
if handler(sessID, sess.lastUserTime) {
delete(se.sessions, sessID)
se.activeSessions.Add(context.Background(), -1)
removedSessions++
} else {
log.Printf("sessID: %d, userTime: %d", sessID, sess.lastUserTime)
}
}
}

View file

@ -1,22 +1,25 @@
package cache
import (
"log"
. "openreplay/backend/pkg/messages"
"time"
// . "openreplay/backend/pkg/db/types"
)
func (c *PGCache) insertSessionEnd(sessionID uint64, timestamp uint64) error {
//duration, err := c.Conn.InsertSessionEnd(sessionID, timestamp)
func (c *PGCache) InsertSessionEnd(sessionID uint64, timestamp uint64) error {
_, err := c.Conn.InsertSessionEnd(sessionID, timestamp)
if err != nil {
return err
}
return nil
}
func (c *PGCache) HandleSessionEnd(sessionID uint64) error {
if err := c.Conn.HandleSessionEnd(sessionID); err != nil {
log.Printf("can't handle session end: %s", err)
}
c.DeleteSession(sessionID)
// session, err := c.GetSession(sessionID)
// if err != nil {
// return err
// }
// session.Duration = &duration
return nil
}
@ -45,6 +48,12 @@ func (c *PGCache) InsertMetadata(sessionID uint64, metadata *Metadata) error {
return nil
}
if err := c.Conn.InsertMetadata(sessionID, keyNo, metadata.Value); err != nil {
// Try to insert metadata after one minute
time.AfterFunc(time.Minute, func() {
if err := c.Conn.InsertMetadata(sessionID, keyNo, metadata.Value); err != nil {
log.Printf("metadata retry err: %s", err)
}
})
return err
}
session.SetMetadata(keyNo, metadata.Value)

View file

@ -32,7 +32,7 @@ func (c *PGCache) InsertIOSSessionStart(sessionID uint64, s *IOSSessionStart) er
}
func (c *PGCache) InsertIOSSessionEnd(sessionID uint64, e *IOSSessionEnd) error {
return c.insertSessionEnd(sessionID, e.Timestamp)
return c.InsertSessionEnd(sessionID, e.Timestamp)
}
func (c *PGCache) InsertIOSScreenEnter(sessionID uint64, screenEnter *IOSScreenEnter) error {

View file

@ -7,6 +7,30 @@ import (
)
func (c *PGCache) InsertWebSessionStart(sessionID uint64, s *SessionStart) error {
return c.Conn.InsertSessionStart(sessionID, &Session{
SessionID: sessionID,
Platform: "web",
Timestamp: s.Timestamp,
ProjectID: uint32(s.ProjectID),
TrackerVersion: s.TrackerVersion,
RevID: s.RevID,
UserUUID: s.UserUUID,
UserOS: s.UserOS,
UserOSVersion: s.UserOSVersion,
UserDevice: s.UserDevice,
UserCountry: s.UserCountry,
// web properties (TODO: unite different platform types)
UserAgent: s.UserAgent,
UserBrowser: s.UserBrowser,
UserBrowserVersion: s.UserBrowserVersion,
UserDeviceType: s.UserDeviceType,
UserDeviceMemorySize: s.UserDeviceMemorySize,
UserDeviceHeapSize: s.UserDeviceHeapSize,
UserID: &s.UserID,
})
}
func (c *PGCache) HandleWebSessionStart(sessionID uint64, s *SessionStart) error {
if c.sessions[sessionID] != nil {
return errors.New("This session already in cache!")
}
@ -31,7 +55,7 @@ func (c *PGCache) InsertWebSessionStart(sessionID uint64, s *SessionStart) error
UserDeviceHeapSize: s.UserDeviceHeapSize,
UserID: &s.UserID,
}
if err := c.Conn.InsertSessionStart(sessionID, c.sessions[sessionID]); err != nil {
if err := c.Conn.HandleSessionStart(sessionID, c.sessions[sessionID]); err != nil {
c.sessions[sessionID] = nil
return err
}
@ -39,7 +63,11 @@ func (c *PGCache) InsertWebSessionStart(sessionID uint64, s *SessionStart) error
}
func (c *PGCache) InsertWebSessionEnd(sessionID uint64, e *SessionEnd) error {
return c.insertSessionEnd(sessionID, e.Timestamp)
return c.InsertSessionEnd(sessionID, e.Timestamp)
}
func (c *PGCache) HandleWebSessionEnd(sessionID uint64, e *SessionEnd) error {
return c.HandleSessionEnd(sessionID)
}
func (c *PGCache) InsertWebErrorEvent(sessionID uint64, e *ErrorEvent) error {

View file

@ -3,6 +3,7 @@ package postgres
import (
"context"
"log"
"strings"
"time"
"github.com/jackc/pgx/v4"
@ -14,24 +15,33 @@ func getTimeoutContext() context.Context {
return ctx
}
type batchItem struct {
query string
arguments []interface{}
}
type Conn struct {
c *pgxpool.Pool // TODO: conditional usage of Pool/Conn (use interface?)
batches map[uint64]*pgx.Batch
batchSizes map[uint64]int
rawBatches map[uint64][]*batchItem
batchQueueLimit int
batchSizeLimit int
}
func NewConn(url string) *Conn {
func NewConn(url string, queueLimit, sizeLimit int) *Conn {
c, err := pgxpool.Connect(context.Background(), url)
if err != nil {
log.Println(err)
log.Fatalln("pgxpool.Connect Error")
}
return &Conn{
c: c,
batches: make(map[uint64]*pgx.Batch),
batchSizes: make(map[uint64]int),
c: c,
batches: make(map[uint64]*pgx.Batch),
batchSizes: make(map[uint64]int),
rawBatches: make(map[uint64][]*batchItem),
batchQueueLimit: queueLimit,
batchSizeLimit: sizeLimit,
}
}
@ -44,9 +54,17 @@ func (conn *Conn) batchQueue(sessionID uint64, sql string, args ...interface{})
batch, ok := conn.batches[sessionID]
if !ok {
conn.batches[sessionID] = &pgx.Batch{}
conn.rawBatches[sessionID] = make([]*batchItem, 0)
batch = conn.batches[sessionID]
}
batch.Queue(sql, args...)
// Temp raw batch store
raw := conn.rawBatches[sessionID]
raw = append(raw, &batchItem{
query: sql,
arguments: args,
})
conn.rawBatches[sessionID] = raw
}
func (conn *Conn) CommitBatches() {
@ -56,12 +74,16 @@ func (conn *Conn) CommitBatches() {
for i := 0; i < l; i++ {
if ct, err := br.Exec(); err != nil {
log.Printf("Error in PG batch (command tag %s, session: %d): %v \n", ct.String(), sessID, err)
failedSql := conn.rawBatches[sessID][i]
query := strings.ReplaceAll(failedSql.query, "\n", " ")
log.Println("failed sql req:", query, failedSql.arguments)
}
}
br.Close() // returns err
}
conn.batches = make(map[uint64]*pgx.Batch)
conn.batchSizes = make(map[uint64]int)
conn.rawBatches = make(map[uint64][]*batchItem)
}
func (conn *Conn) updateBatchSize(sessionID uint64, reqSize int) {
@ -83,6 +105,9 @@ func (conn *Conn) commitBatch(sessionID uint64) {
for i := 0; i < l; i++ {
if ct, err := br.Exec(); err != nil {
log.Printf("Error in PG batch (command tag %s, session: %d): %v \n", ct.String(), sessionID, err)
failedSql := conn.rawBatches[sessionID][i]
query := strings.ReplaceAll(failedSql.query, "\n", " ")
log.Println("failed sql req:", query, failedSql.arguments)
}
}
br.Close()
@ -90,6 +115,7 @@ func (conn *Conn) commitBatch(sessionID uint64) {
// Clean batch info
delete(conn.batches, sessionID)
delete(conn.batchSizes, sessionID)
delete(conn.rawBatches, sessionID)
}
func (conn *Conn) query(sql string, args ...interface{}) (pgx.Rows, error) {

View file

@ -2,6 +2,7 @@ package postgres
import (
"fmt"
"log"
"strings"
"openreplay/backend/pkg/db/types"
@ -31,14 +32,17 @@ func (conn *Conn) insertAutocompleteValue(sessionID uint64, tp string, value str
FROM sessions
WHERE session_id = $3
) ON CONFLICT DO NOTHING`
conn.batchQueue(sessionID, sqlRequest, value, tp, sessionID)
if err := conn.exec(sqlRequest, value, tp, sessionID); err != nil {
log.Printf("can't insert autocomplete: %s", err)
}
//conn.batchQueue(sessionID, sqlRequest, value, tp, sessionID)
// Record approximate message size
conn.updateBatchSize(sessionID, len(sqlRequest)+len(value)+len(tp)+8)
//conn.updateBatchSize(sessionID, len(sqlRequest)+len(value)+len(tp)+8)
}
func (conn *Conn) InsertSessionStart(sessionID uint64, s *types.Session) error {
if err := conn.exec(`
return conn.exec(`
INSERT INTO sessions (
session_id, project_id, start_ts,
user_uuid, user_device, user_device_type, user_country,
@ -66,9 +70,10 @@ func (conn *Conn) InsertSessionStart(sessionID uint64, s *types.Session) error {
s.Platform,
s.UserAgent, s.UserBrowser, s.UserBrowserVersion, s.UserDeviceMemorySize, s.UserDeviceHeapSize,
s.UserID,
); err != nil {
return err
}
)
}
func (conn *Conn) HandleSessionStart(sessionID uint64, s *types.Session) error {
conn.insertAutocompleteValue(sessionID, getAutocompleteType("USEROS", s.Platform), s.UserOS)
conn.insertAutocompleteValue(sessionID, getAutocompleteType("USERDEVICE", s.Platform), s.UserDevice)
conn.insertAutocompleteValue(sessionID, getAutocompleteType("USERCOUNTRY", s.Platform), s.UserCountry)
@ -79,6 +84,20 @@ func (conn *Conn) InsertSessionStart(sessionID uint64, s *types.Session) error {
}
func (conn *Conn) InsertSessionEnd(sessionID uint64, timestamp uint64) (uint64, error) {
var dur uint64
if err := conn.queryRow(`
UPDATE sessions SET duration=$2 - start_ts
WHERE session_id=$1
RETURNING duration
`,
sessionID, timestamp,
).Scan(&dur); err != nil {
return 0, err
}
return dur, nil
}
func (conn *Conn) HandleSessionEnd(sessionID uint64) error {
// TODO: search acceleration?
sqlRequest := `
UPDATE sessions
@ -96,18 +115,7 @@ func (conn *Conn) InsertSessionEnd(sessionID uint64, timestamp uint64) (uint64,
// Record approximate message size
conn.updateBatchSize(sessionID, len(sqlRequest)+8)
var dur uint64
if err := conn.queryRow(`
UPDATE sessions SET duration=$2 - start_ts
WHERE session_id=$1
RETURNING duration
`,
sessionID, timestamp,
).Scan(&dur); err != nil {
return 0, err
}
return dur, nil
return nil
}
func (conn *Conn) InsertRequest(sessionID uint64, timestamp uint64, index uint64, url string, duration uint64, success bool) error {
@ -115,7 +123,7 @@ func (conn *Conn) InsertRequest(sessionID uint64, timestamp uint64, index uint64
INSERT INTO events_common.requests (
session_id, timestamp, seq_index, url, duration, success
) VALUES (
$1, $2, $3, $4, $5, $6
$1, $2, $3, left($4, 2700), $5, $6
)`
conn.batchQueue(sessionID, sqlRequest, sessionID, timestamp, getSqIdx(index), url, duration, success)
@ -129,7 +137,7 @@ func (conn *Conn) InsertCustomEvent(sessionID uint64, timestamp uint64, index ui
INSERT INTO events_common.customs (
session_id, timestamp, seq_index, name, payload
) VALUES (
$1, $2, $3, $4, $5
$1, $2, $3, left($4, 2700), $5
)`
conn.batchQueue(sessionID, sqlRequest, sessionID, timestamp, getSqIdx(index), name, payload)
@ -222,7 +230,7 @@ func (conn *Conn) InsertIssueEvent(sessionID uint64, projectID uint32, e *messag
INSERT INTO events_common.customs
(session_id, seq_index, timestamp, name, payload, level)
VALUES
($1, $2, $3, $4, $5, 'error')
($1, $2, $3, left($4, 2700), $5, 'error')
`,
sessionID, getSqIdx(e.MessageID), e.Timestamp, e.ContextString, e.Payload,
); err != nil {

View file

@ -1,6 +1,7 @@
package postgres
import (
"log"
. "openreplay/backend/pkg/messages"
"openreplay/backend/pkg/url"
)
@ -27,16 +28,25 @@ func (conn *Conn) InsertWebStatsPerformance(sessionID uint64, p *PerformanceTrac
$10, $11, $12,
$13, $14, $15
)`
conn.batchQueue(sessionID, sqlRequest,
//conn.batchQueue(sessionID, sqlRequest,
// sessionID, timestamp, timestamp, // ??? TODO: primary key by timestamp+session_id
// p.MinFPS, p.AvgFPS, p.MaxFPS,
// p.MinCPU, p.AvgCPU, p.MinCPU,
// p.MinTotalJSHeapSize, p.AvgTotalJSHeapSize, p.MaxTotalJSHeapSize,
// p.MinUsedJSHeapSize, p.AvgUsedJSHeapSize, p.MaxUsedJSHeapSize,
//)
if err := conn.exec(sqlRequest,
sessionID, timestamp, timestamp, // ??? TODO: primary key by timestamp+session_id
p.MinFPS, p.AvgFPS, p.MaxFPS,
p.MinCPU, p.AvgCPU, p.MinCPU,
p.MinTotalJSHeapSize, p.AvgTotalJSHeapSize, p.MaxTotalJSHeapSize,
p.MinUsedJSHeapSize, p.AvgUsedJSHeapSize, p.MaxUsedJSHeapSize,
)
); err != nil {
log.Printf("can't insert perf: %s", err)
}
// Record approximate message size
conn.updateBatchSize(sessionID, len(sqlRequest)+8*15)
//conn.updateBatchSize(sessionID, len(sqlRequest)+8*15)
return nil
}
@ -57,7 +67,7 @@ func (conn *Conn) InsertWebStatsResourceEvent(sessionID uint64, e *ResourceEvent
) VALUES (
$1, $2, $3,
$4,
$5, $6, $7,
left($5, 2700), $6, $7,
$8, $9,
NULLIF($10, '')::events.resource_method,
NULLIF($11, 0), NULLIF($12, 0), NULLIF($13, 0), NULLIF($14, 0), NULLIF($15, 0)

View file

@ -229,7 +229,7 @@ func (conn *Conn) InsertWebFetchEvent(sessionID uint64, savePayload bool, e *Fet
duration, success
) VALUES (
$1, $2, $3,
$4, $5, $6, $7,
left($4, 2700), $5, $6, $7,
$8, $9, $10::smallint, NULLIF($11, '')::http_method,
$12, $13
) ON CONFLICT DO NOTHING`
@ -242,7 +242,7 @@ func (conn *Conn) InsertWebFetchEvent(sessionID uint64, savePayload bool, e *Fet
// Record approximate message size
conn.updateBatchSize(sessionID, len(sqlRequest)+len(e.URL)+len(host)+len(path)+len(query)+
len(*request)+len(*response)+len(url.EnsureMethod(e.Method))+8*5+1)
len(e.Request)+len(e.Response)+len(url.EnsureMethod(e.Method))+8*5+1)
return nil
}
@ -261,7 +261,7 @@ func (conn *Conn) InsertWebGraphQLEvent(sessionID uint64, savePayload bool, e *G
request_body, response_body
) VALUES (
$1, $2, $3,
$4,
left($4, 2700),
$5, $6
) ON CONFLICT DO NOTHING`
conn.batchQueue(sessionID, sqlRequest, sessionID, e.Timestamp, e.MessageID,
@ -269,6 +269,6 @@ func (conn *Conn) InsertWebGraphQLEvent(sessionID uint64, savePayload bool, e *G
)
// Record approximate message size
conn.updateBatchSize(sessionID, len(sqlRequest)+len(e.OperationName)+len(*request)+len(*response)+8*3)
conn.updateBatchSize(sessionID, len(sqlRequest)+len(e.OperationName)+len(e.Variables)+len(e.Response)+8*3)
return nil
}

View file

@ -1,11 +1,8 @@
package messages
import (
"fmt"
"io"
"strings"
"github.com/pkg/errors"
"io"
)
func ReadBatchReader(reader io.Reader, messageHandler func(Message)) error {
@ -16,15 +13,7 @@ func ReadBatchReader(reader io.Reader, messageHandler func(Message)) error {
if err == io.EOF {
return nil
} else if err != nil {
if strings.HasPrefix(err.Error(), "Unknown message code:") {
code := strings.TrimPrefix(err.Error(), "Unknown message code: ")
msg, err = DecodeExtraMessage(code, reader)
if err != nil {
return fmt.Errorf("can't decode msg: %s", err)
}
} else {
return errors.Wrapf(err, "Batch Message decoding error on message with index %v", index)
}
return errors.Wrapf(err, "Batch Message decoding error on message with index %v", index)
}
msg = transformDeprecated(msg)

View file

@ -1,41 +1,5 @@
package messages
import (
"bytes"
//"io"
)
func Encode(msg Message) []byte {
return msg.Encode()
}
//
// func EncodeList(msgs []Message) []byte {
// }
//
// func Decode(b []byte) (Message, error) {
// return ReadMessage(bytes.NewReader(b))
// }
// func DecodeEach(b []byte, callback func(Message)) error {
// var err error
// reader := bytes.NewReader(b)
// for {
// msg, err := ReadMessage(reader)
// if err != nil {
// break
// }
// callback(msg)
// }
// if err == io.EOF {
// return nil
// }
// return err
// }
func GetMessageTypeID(b []byte) (uint64, error) {
reader := bytes.NewReader(b)
return ReadUint(reader)
}

View file

@ -571,7 +571,7 @@ func (msg *JSException) TypeID() int {
return 25
}
type RawErrorEvent struct {
type IntegrationEvent struct {
message
Timestamp uint64
Source string
@ -580,7 +580,7 @@ type RawErrorEvent struct {
Payload string
}
func (msg *RawErrorEvent) Encode() []byte {
func (msg *IntegrationEvent) Encode() []byte {
buf := make([]byte, 51+len(msg.Source)+len(msg.Name)+len(msg.Message)+len(msg.Payload))
buf[0] = 26
p := 1
@ -592,7 +592,7 @@ func (msg *RawErrorEvent) Encode() []byte {
return buf[:p]
}
func (msg *RawErrorEvent) TypeID() int {
func (msg *IntegrationEvent) TypeID() int {
return 26
}
@ -1396,7 +1396,7 @@ type IssueEvent struct {
Type string
ContextString string
Context string
Payload string // TODO: check, maybe it's better to use empty interface here
Payload string
}
func (msg *IssueEvent) Encode() []byte {

View file

@ -369,7 +369,7 @@ func ReadMessage(reader io.Reader) (Message, error) {
return msg, nil
case 26:
msg := &RawErrorEvent{}
msg := &IntegrationEvent{}
if msg.Timestamp, err = ReadUint(reader); err != nil {
return nil, err
}

View file

@ -1,35 +0,0 @@
package messages
import (
"fmt"
"io"
)
type SessionFinished struct {
message
Timestamp uint64
}
func (msg *SessionFinished) Encode() []byte {
buf := make([]byte, 11)
buf[0] = 127
p := 1
p = WriteUint(msg.Timestamp, buf, p)
return buf[:p]
}
func (msg *SessionFinished) TypeID() int {
return 127
}
func DecodeExtraMessage(code string, reader io.Reader) (Message, error) {
var err error
if code != "127" {
return nil, fmt.Errorf("unknown message code: %s", code)
}
trigger := &SessionFinished{}
if trigger.Timestamp, err = ReadUint(reader); err != nil {
return nil, fmt.Errorf("can't read message timestamp: %s", err)
}
return trigger, nil
}

View file

@ -49,11 +49,16 @@ func (b *builder) handleMessage(message Message, messageID uint64) {
}
timestamp := GetTimestamp(message)
if timestamp == 0 {
log.Printf("skip message with empty timestamp, sessID: %d, msgID: %d, msgType: %d", b.sessionID, messageID, message.TypeID())
switch message.(type) {
case *SessionEnd, *IssueEvent, *PerformanceTrackAggr:
break
default:
log.Printf("skip message with empty timestamp, sessID: %d, msgID: %d, msgType: %d", b.sessionID, messageID, message.TypeID())
}
return
}
if timestamp < b.timestamp {
log.Printf("skip message with wrong timestamp, sessID: %d, msgID: %d, type: %d, msgTS: %d, lastTS: %d", b.sessionID, messageID, message.TypeID(), timestamp, b.timestamp)
//log.Printf("skip message with wrong timestamp, sessID: %d, msgID: %d, type: %d, msgTS: %d, lastTS: %d", b.sessionID, messageID, message.TypeID(), timestamp, b.timestamp)
} else {
b.timestamp = timestamp
}

View file

@ -10,6 +10,7 @@ func DiscardURLQuery(url string) string {
}
func GetURLParts(rawURL string) (string, string, string, error) {
rawURL = strings.Replace(rawURL, "\t", "", -1) // Other chars?
u, err := _url.Parse(rawURL)
if err != nil {
return "", "", "", err

4
ee/api/.gitignore vendored
View file

@ -247,7 +247,6 @@ Pipfile
/db_changes.sql
/Dockerfile.bundle
/entrypoint.bundle.sh
#/entrypoint.sh
/chalicelib/core/heatmaps.py
/routers/subs/insights.py
/schemas.py
@ -258,5 +257,4 @@ Pipfile
/build_alerts.sh
/routers/subs/metrics.py
/routers/subs/v1_api.py
/chalicelib/core/dashboards.py
entrypoint.sh
/chalicelib/core/dashboards.py

6
ee/api/entrypoint.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/bash
bash env_vars.sh
cd sourcemap-reader
nohup npm start &> /tmp/sourcemap-reader.log &
cd ..
uvicorn app:app --host 0.0.0.0 --reload --proxy-headers

27
ee/api/env_vars.sh Executable file
View file

@ -0,0 +1,27 @@
#!/bin/bash
if [[ -z "${ENV_CONFIG_OVERRIDE_PATH}" ]]; then
echo 'no env-override'
else
override=$ENV_CONFIG_OVERRIDE_PATH
if [ -f "$override" ]; then
# to remove endOfLine form sed result
echo "" >> $override
sed 's/=.*//;/^$/d' $override > .replacements
# to remove all defined os-env-vars
cat .replacements | while read line
do
unset $line
done
rm .replacements
# to merge predefined .env with the override.env
cp .env .env.d
sort -u -t '=' -k 1,1 $override .env.d > .env
rm .env.d
else
echo "$override does not exist."
fi
fi

7
frontend/.dockerignore Normal file
View file

@ -0,0 +1,7 @@
node_modules
npm-debug.log
.git
.cache
**/build.sh
**/build_*.sh
**/*deploy.sh

18
frontend/Dockerfile Normal file
View file

@ -0,0 +1,18 @@
from node:14-stretch-slim AS builder
workdir /work
COPY . .
RUN cp .env.sample .env
RUN yarn
RUN yarn build
FROM nginx:alpine as cicd
LABEL maintainer=Rajesh<rajesh@openreplay.com>
COPY public /var/www/openreplay
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Default step in docker build
FROM nginx:alpine
LABEL maintainer=Rajesh<rajesh@openreplay.com>
COPY --from=builder /work/public /var/www/openreplay
COPY nginx.conf /etc/nginx/conf.d/default.conf

View file

@ -2,7 +2,7 @@
OpenReplay prototype UI
On new icon addition:
`npm run generate:icons`
`yarn gen:icons`
## Documentation

View file

@ -1,181 +0,0 @@
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// const CircularDependencyPlugin = require('circular-dependency-plugin')
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const MomentLocalesPlugin = require('moment-locales-webpack-plugin'); //TODO: replace Moment with date-fns ??
const path = require('path');
const fs = require('fs');
const alias = require('./path-alias');
const environments = require('./env');
const DIST_DIR = 'public';
const GLOBAL_STYLES_DIR = 'app/styles';
const cssEntrypoints = [
'codemirror/lib/codemirror.css',
'codemirror/theme/yeti.css',
'codemirror/addon/lint/lint.css',
'react-daterange-picker/dist/css/react-calendar.css',
'react-datepicker/dist/react-datepicker.css',
'rc-time-picker/assets/index.css',
];
const babelLoader = {
loader: 'babel-loader',
options: {
presets: [
[ '@babel/preset-env', { // probably, use dynamic imports for polifills in future
"targets": "> 4%, not dead",
useBuiltIns: 'entry',
corejs: 3
}],
'@babel/preset-react',
"@babel/preset-flow", //TODO: remove, use ts
],
plugins: [
"@babel/plugin-syntax-bigint",
["@babel/plugin-proposal-private-property-in-object", { "loose": true }],
[ '@babel/plugin-proposal-decorators', { legacy: true } ],
[ '@babel/plugin-proposal-class-properties', { loose: true }],
[ '@babel/plugin-proposal-private-methods', { loose: true }],
// 'recharts'
]
}
};
const cssFiles = fs.readdirSync(GLOBAL_STYLES_DIR, { withFileTypes: true });
cssFiles.forEach(file => {
if (/.css$/.test(file.name)) {
const pathFullName = path.join(__dirname, GLOBAL_STYLES_DIR, file.name);
cssEntrypoints.push(pathFullName);
}
});
function prepareEnv(env) {
const pEnv = {};
Object.keys(env).forEach(key => {
pEnv[ `window.ENV.${ key }` ] = typeof env[ key ] === 'function' ? env[ key ]() : JSON.stringify(env[ key ]);
});
return pEnv;
}
module.exports = (envName = 'local') => {
const env = environments[ envName ];
const cssFileLoader = {
loader: MiniCssExtractPlugin.loader,
options: {
hmr: !env.PRODUCTION,
},
}
return {
// Polyfill only for async (TODO)
entry: [ './app/initialize.js' ].concat(cssEntrypoints),
output: {
path: path.join(__dirname, DIST_DIR),
filename: 'app-[contenthash:7].js',
publicPath: '/',
},
plugins: [
new webpack.ProvidePlugin({
'React': 'react' // back code compatability
}),
new MiniCssExtractPlugin({
path: path.join(__dirname, DIST_DIR),
filename: 'app-[contenthash:7].css'
}),
new CopyWebpackPlugin([ 'app/assets' ]),
new HtmlWebpackPlugin({
template: 'app/assets/index.html'
}),
new MomentLocalesPlugin(),
new webpack.DefinePlugin(prepareEnv(env)),
// new BundleAnalyzerPlugin({ analyzerMode: 'static'}),
// new CircularDependencyPlugin({
// // exclude detection of files based on a RegExp
// exclude: /node_modules/,
// // add errors to webpack instead of warnings
// failOnError: true,
// // allow import cycles that include an asyncronous import,
// // e.g. via import(/* webpackMode: "weak" */ './file.js')
// allowAsyncCycles: false,
// // set the current working directory for displaying module paths
// cwd: process.cwd(),
// })
],
module: {
rules: [
// global and module css separation. TODO more beautyfull
{
test: /\.css$/,
include: [ path.join(__dirname, "app/components"), path.join(__dirname, "app/player") ],
use: [
cssFileLoader,
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]_[local]_[hash:base64:7]'
},
}
},
'postcss-loader'
]
},
{
test: /\.css$/,
include: [ path.join(__dirname, "node_modules"), path.join(__dirname, "app/styles") ],
use: [
cssFileLoader,
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
'postcss-loader'
]
},
{
test: /\.svg$/,
use: ['@svgr/webpack'],
},
{
test: /\.js$/,
include: [ path.join(__dirname, "app"), path.join(__dirname, ".storybook") ],
use: babelLoader,
},
{
test: /\.tsx?$/,
include: path.join(__dirname, "app"),
use: [ 'ts-loader' ]
},
]
},
resolve: {
alias,
extensions: ['.js', '.json', '.ts', '.tsx' ],
},
mode: env.PRODUCTION ? 'production' : 'development',
optimization: {
splitChunks: {
chunks: 'all',
},
},
devServer: {
contentBase: path.join(__dirname, DIST_DIR),
//compress: true,
port: 3333,
historyApiFallback: true,
},
stats: 'errors-only',
devtool: env.SOURCEMAP && 'source-map'
};
}

View file

@ -145,7 +145,7 @@ class Router extends React.Component {
}
componentDidUpdate(prevProps, prevState) {
this.props.setSessionPath(prevProps.location.pathname)
this.props.setSessionPath(prevProps.location)
if (prevProps.email !== this.props.email && !this.props.email) {
this.props.fetchTenants();
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#d7e2e2;}.cls-3{fill:#9da0a0;}.cls-4{fill:#fab29a;}.cls-5{fill:#222;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M114,0H6A6,6,0,0,0,0,6V114a6,6,0,0,0,6,6H114a6,6,0,0,0,6-6V6A6,6,0,0,0,114,0Z"/><path class="cls-2" d="M28,108.75H91a8,8,0,0,0,8-8v-64a8,8,0,0,0-8-8H55.67L35,12V28.81H28a8,8,0,0,0-8,8v64a8,8,0,0,0,8,8Z"/><path class="cls-3" d="M54.11,79.63H64.89A13.25,13.25,0,0,1,78.15,92.88v7.2H40.85v-7.2A13.25,13.25,0,0,1,54.11,79.63Z"/><path class="cls-4" d="M46.18,53.82H72.82V66.3a13.32,13.32,0,1,1-26.64,0Z"/><path class="cls-5" d="M76.15,55v6.93a65,65,0,0,1-22.58-4.94,14.93,14.93,0,0,1-10.72,5V55a16.67,16.67,0,0,1,33.33,0Z"/><path d="M59.67,41.83A13.55,13.55,0,0,0,46.11,55.39V58.1h2.71a2.72,2.72,0,0,1,1.92.8,2.75,2.75,0,0,1,.79,1.91V69a2.75,2.75,0,0,1-.79,1.92,2.71,2.71,0,0,1-1.92.79H46.11A2.71,2.71,0,0,1,43.39,69V55.39a16.23,16.23,0,0,1,4.77-11.5,16.26,16.26,0,0,1,23,0,16.23,16.23,0,0,1,4.77,11.5V71.66a6.78,6.78,0,0,1-6.78,6.78H63.37A2.68,2.68,0,0,1,61,79.8H58.31a2.72,2.72,0,0,1-1.92-.8,2.67,2.67,0,0,1-.79-1.91,2.71,2.71,0,0,1,2.71-2.72H61a2.7,2.7,0,0,1,1.36.37,2.76,2.76,0,0,1,1,1h5.79a4.08,4.08,0,0,0,4.07-4.07H70.51a2.67,2.67,0,0,1-1.91-.79A2.72,2.72,0,0,1,67.8,69V60.81a2.71,2.71,0,0,1,.8-1.91,2.68,2.68,0,0,1,1.91-.8h2.72V55.39a13.61,13.61,0,0,0-4-9.59,13.44,13.44,0,0,0-4.39-2.94,13.61,13.61,0,0,0-5.19-1Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1 @@
<svg width="2500" height="1719" viewBox="0 0 256 176" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M57.838 170.017c.151 1.663-.051 3.789-.14 5.436h56.864c.053-1.654.091-3.311.091-4.974 0-39.942-15.768-76.266-44.011-104.51C56.704 52.032 40.885 41.31 23.246 33.898L0 86.328c33.989 15.82 54.211 43.783 57.838 83.689zm69.197-1.644c.108 2.371-.062 4.732-.167 7.08h58.177c.077-2.355.13-4.714.13-7.08 0-28.826-5.66-56.82-16.82-83.207-10.767-25.456-26.169-48.306-45.778-67.915a216.421 216.421 0 0 0-15.686-14.218l-37.68 44.315c37.293 33.313 55.304 65.858 57.824 121.025zM235.263 64.39C226.595 41.785 213.935 19.521 198.727 0l-46.95 34.442c27.495 35.099 44.442 79.71 46.058 127.612.152 4.502-.164 8.969-.457 13.399h58.252c.226-4.448.447-8.916.344-13.399-.805-34.945-8.23-65.12-20.71-97.665z" fill="#3676A1"/></svg>

After

Width:  |  Height:  |  Size: 835 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="3.62 8.78 64 64"><path d="M35.605 72.78L63.9 52.803l-28.266-4.13-28.296 4.16z" fill="#b7ca9d"/><path d="M12.986 21.167v31.18l4.797 1.427L29.017 36.56 17.783 19.315z" fill="#4c622c"/><path d="M34.694 23.14v27.72l-16.9 2.975v-34.52z" fill="#759c3f"/><path d="M46.686 33.98l-19.52 22.68-6.62-2.004V12.1l6.62-3.3z" fill="#4c622c"/><path d="M49.753 17.585v33.822L27.165 56.7V8.78z" fill="#759c3f"/><path d="M35.605 72.78v-9.928L7.34 52.833v5.8z" fill="#4c622c"/><path d="M63.9 52.803v5.83L35.605 72.78v-9.928z" fill="#759c3f"/><path d="M35.605 59.178l20.342-16h-6.86l-19.522 1.336z" fill="#b7ca9d"/><path d="M29.563 44.514v12.842l6.042 1.822V45.152z" fill="#4c622c"/><path d="M55.946 53.076v-9.898L35.604 45.15v14.027z" fill="#759c3f"/></svg>

After

Width:  |  Height:  |  Size: 786 B

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<path id="Bits" fill-rule="evenodd" clip-rule="evenodd" fill="#774AA4" d="M350.2,268.3l-27.3-18.1l-22.8,38.1l-26.5-7.8
l-23.3,35.7l1.2,11.2l126.7-23.4l-7.4-79.4L350.2,268.3z M232,234l20.3-2.8c3.3,1.5,5.6,2,9.5,3.1c6.1,1.6,13.3,3.1,23.8-2.2
c2.5-1.2,7.6-5.9,9.6-8.6l83.3-15.2l8.5,103.2l-142.7,25.8L232,234z M386.7,196.8l-8.2,1.6L362.7,34.7L93.5,66l33.2,270l31.5-4.6
c-2.5-3.6-6.4-8-13.1-13.5c-9.3-7.7-6-20.9-0.5-29.2c7.2-14,44.6-31.8,42.4-54.2c-0.8-8.1-2-18.7-9.6-26c-0.3,3,0.2,5.9,0.2,5.9
s-3.1-4-4.6-9.4c-1.5-2.1-2.7-2.7-4.4-5.5c-1.2,3.2-1,6.9-1,6.9s-2.5-6-2.9-11.1c-1.5,2.3-1.9,6.6-1.9,6.6s-3.3-9.5-2.5-14.6
c-1.5-4.4-6-13.2-4.7-33.2c8.2,5.8,26.3,4.4,33.4-6c2.3-3.5,3.9-12.9-1.2-31.4c-3.3-11.9-11.4-29.6-14.6-36.4l-0.4,0.3
c1.7,5.4,5.1,16.8,6.4,22.3c4,16.7,5.1,22.5,3.2,30.2c-1.6,6.7-5.4,11.1-15.1,16c-9.7,4.9-22.6-7-23.4-7.7
c-9.4-7.5-16.7-19.8-17.5-25.8c-0.8-6.5,3.8-10.5,6.1-15.8c-3.3,1-7,2.6-7,2.6s4.4-4.6,9.9-8.6c2.3-1.5,3.6-2.5,6-4.4
c-3.4-0.1-6.2,0-6.2,0s5.7-3.1,11.7-5.4c-4.4-0.2-8.5,0-8.5,0s12.8-5.7,22.9-10c7-2.9,13.8-2,17.6,3.5c5,7.3,10.3,11.2,21.4,13.6
c6.9-3,8.9-4.6,17.5-7c7.6-8.4,13.5-9.4,13.5-9.4s-3,2.7-3.7,7c4.3-3.4,9-6.2,9-6.2s-1.8,2.3-3.5,5.8l0.4,0.6c5-3,10.9-5.4,10.9-5.4
s-1.7,2.1-3.7,4.9c3.8,0,11.5,0.2,14.4,0.5c17.6,0.4,21.2-18.8,28-21.2c8.4-3,12.2-4.9,26.6,9.3c12.3,12.2,22,33.9,17.2,38.8
c-4,4-11.9-1.6-20.7-12.6c-4.6-5.8-8.1-12.7-9.8-21.4c-1.4-7.4-6.8-11.6-6.8-11.6s3.1,7,3.1,13.2c0,3.4,0.4,16,5.8,23
c-0.5,1-0.8,5.1-1.4,5.9c-6.3-7.6-19.7-13-21.9-14.6c7.4,6.1,24.5,20.1,31.1,33.6c6.2,12.7,2.5,24.4,5.7,27.4
c0.9,0.9,13.3,16.4,15.7,24.2c4.2,13.6,0.2,27.9-5.2,36.8l-15.3,2.4c-2.2-0.6-3.7-0.9-5.7-2.1c1.1-2,3.3-6.8,3.3-7.9l-0.9-1.5
c-4.7,6.7-12.7,13.3-19.3,17.1c-8.7,4.9-18.6,4.2-25.1,2.1c-18.4-5.7-35.9-18.2-40.1-21.5c0,0-0.1,2.6,0.7,3.2
c4.6,5.3,15.3,14.8,25.6,21.4l-21.9,2.4l10.4,81c-4.6,0.7-5.3,1-10.3,1.7c-4.4-15.7-12.9-26-22.2-32c-8.2-5.3-19.5-6.5-30.3-4.3
l-0.7,0.8c7.5-0.8,16.4,0.3,25.5,6.1c8.9,5.7,16.1,20.3,18.8,29.1c3.4,11.3,5.7,23.3-3.4,36.1c-6.5,9.1-25.5,14.1-40.8,3.2
c4.1,6.6,9.6,12,17.1,13c11.1,1.5,21.6-0.4,28.8-7.9c6.2-6.4,9.4-19.7,8.6-33.7l9.8-1.4l3.5,25.2l161.6-19.5L386.7,196.8z
M288.4,128.5c-0.5,1-1.2,1.7-0.1,5.1l0.1,0.2l0.2,0.4l0.4,1c1.9,3.9,4,7.6,7.5,9.5c0.9-0.2,1.9-0.3,2.8-0.3
c3.3-0.1,5.4,0.4,6.7,1.1c0.1-0.7,0.1-1.6,0.1-3.1c-0.3-5,1-13.5-8.6-17.9c-3.6-1.7-8.7-1.2-10.3,0.9c0.3,0,0.6,0.1,0.8,0.2
C290.6,126.6,288.8,127.5,288.4,128.5 M315.2,175c-1.3-0.7-7.1-0.4-11.2,0.1c-7.8,0.9-16.3,3.7-18.2,5.1c-3.4,2.6-1.8,7.2,0.7,9
c7,5.2,13.1,8.7,19.6,7.9c4-0.5,7.5-6.8,9.9-12.5C317.6,180.7,317.6,176.4,315.2,175 M245.8,134.7c2.2-2.1-11-4.9-21.3,2.1
c-7.6,5.2-7.8,16.3-0.6,22.6c0.7,0.6,1.3,1.1,1.9,1.4c2.1-1,4.5-2,7.3-2.9c4.7-1.5,8.6-2.3,11.8-2.7c1.5-1.7,3.3-4.7,2.9-10.2
C247.2,137.6,241.6,138.7,245.8,134.7"/>
<path id="Text" fill-rule="evenodd" clip-rule="evenodd" fill="#774AA4" d="M69.9,435.7H43.7v-60.4h26.2c18.9,0,28.4,9.5,28.4,28.6
C98.3,425.1,88.9,435.7,69.9,435.7 M54.9,426h13.3c12.6,0,18.8-7.4,18.8-22.1c0-12.6-6.3-18.9-18.8-18.9H54.9V426z M110.1,435.7
H98.6l25.7-60.4h12.1l26.3,60.4h-12.1l-7.6-16.5h-19.4l3.9-9.7h12.6l-9.9-22.7L110.1,435.7z M156.2,375.3h45.9v9.7h-17.4v50.7h-11.2
V385h-17.4V375.3z M207.9,435.7h-11.5l25.7-60.4h12.1l26.3,60.4h-12.1l-7.6-16.5h-19.4l3.8-9.7h12.6l-9.9-22.7L207.9,435.7z
M294.1,435.7h-26.2v-60.4h26.2c18.9,0,28.4,9.5,28.4,28.6C322.5,425.1,313.1,435.7,294.1,435.7 M279.1,426h13.3
c12.6,0,18.8-7.4,18.8-22.1c0-12.6-6.3-18.9-18.8-18.9h-13.3V426z M330.1,405.6c0-20.5,10.1-30.7,30.4-30.7
c20,0,29.9,10.2,29.9,30.7c0,20.4-10,30.6-29.9,30.6C341.2,436.1,331,426,330.1,405.6 M360.5,426.4c12.2,0,18.3-7,18.3-21.1
c0-13.8-6.1-20.8-18.3-20.8c-12.5,0-18.8,6.9-18.8,20.8C341.8,419.4,348,426.4,360.5,426.4 M437.3,411.3v14.1c-2.6,0.7-4.9,1-6.9,1
c-13.7,0-20.6-7.2-20.6-21.8c0-13.4,7.3-20.1,21.8-20.1c6.1,0,11.7,1.1,16.9,3.4v-10.1c-5.2-2-11.1-3-17.8-3
c-21.7,0-32.6,9.9-32.6,29.8c0,21,10.7,31.5,32.1,31.5c7.4,0,13.5-1.1,18.3-3.2v-31.6h-18.1l-3.8,9.9H437.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -0,0 +1 @@
<svg width="2500" height="2500" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><path d="M255.96 134.393c0-21.521-13.373-40.117-33.223-47.43a75.239 75.239 0 0 0 1.253-13.791c0-39.909-32.386-72.295-72.295-72.295-23.193 0-44.923 11.074-58.505 30.088-6.686-5.224-14.835-7.94-23.402-7.94-21.104 0-38.446 17.133-38.446 38.446 0 4.597.836 9.194 2.298 13.373C13.582 81.739 0 100.962 0 122.274c0 21.522 13.373 40.327 33.431 47.64-.835 4.388-1.253 8.985-1.253 13.79 0 39.7 32.386 72.087 72.086 72.087 23.402 0 44.924-11.283 58.505-30.088 6.686 5.223 15.044 8.149 23.611 8.149 21.104 0 38.446-17.134 38.446-38.446 0-4.597-.836-9.194-2.298-13.373 19.64-7.104 33.431-26.327 33.431-47.64z" fill="#FFF"/><path d="M100.085 110.364l57.043 26.119 57.669-50.565a64.312 64.312 0 0 0 1.253-12.746c0-35.52-28.834-64.355-64.355-64.355-21.313 0-41.162 10.447-53.072 27.998l-9.612 49.73 11.074 23.82z" fill="#F4BD19"/><path d="M40.953 170.75c-.835 4.179-1.253 8.567-1.253 12.955 0 35.52 29.043 64.564 64.564 64.564 21.522 0 41.372-10.656 53.49-28.208l9.403-49.729-12.746-24.238-57.251-26.118-56.207 50.774z" fill="#3CBEB1"/><path d="M40.536 71.918l39.073 9.194 8.775-44.506c-5.432-4.179-11.91-6.268-18.805-6.268-16.925 0-30.924 13.79-30.924 30.924 0 3.552.627 7.313 1.88 10.656z" fill="#E9478C"/><path d="M37.192 81.32c-17.551 5.642-29.67 22.567-29.67 40.954 0 17.97 11.074 34.059 27.79 40.327l54.953-49.73-10.03-21.52-43.043-10.03z" fill="#2C458F"/><path d="M167.784 219.852c5.432 4.18 11.91 6.478 18.596 6.478 16.925 0 30.924-13.79 30.924-30.924 0-3.761-.627-7.314-1.88-10.657l-39.073-9.193-8.567 44.296z" fill="#95C63D"/><path d="M175.724 165.317l43.043 10.03c17.551-5.85 29.67-22.566 29.67-40.954 0-17.97-11.074-33.849-27.79-40.326l-56.415 49.311 11.492 21.94z" fill="#176655"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg"><rect fill="#fff" height="120" rx="6.01" width="120"/><path d="m66.84 29.17 16.44 9.5a7 7 0 0 1 1.62-1.28 7.13 7.13 0 1 1 7.1 12.35 7 7 0 0 1 -1.9.76v19a7.13 7.13 0 1 1 -5.25 13.11 6.85 6.85 0 0 1 -1.76-1.43l-16.32 9.44a7.26 7.26 0 0 1 .36 2.25 7.13 7.13 0 1 1 -14-2l-16.42-9.51a7 7 0 0 1 -1.59 1.25 7.13 7.13 0 1 1 -5.25-13.11v-19a7 7 0 0 1 -1.9-.76 7.13 7.13 0 1 1 7.13-12.35 7 7 0 0 1 1.61 1.27l16.45-9.5a7.22 7.22 0 0 1 -.29-2 7.13 7.13 0 1 1 14.26 0 7.24 7.24 0 0 1 -.29 2.01zm-1.72 2.92 16.49 9.52a7.12 7.12 0 0 0 5.13 8.87v19l-.27.07-21.54-37.27zm-10.05.2-21.53 37.3-.28-.08v-19a7.06 7.06 0 0 0 4.45-3.35 7.15 7.15 0 0 0 .68-5.53l16.48-9.51a1.89 1.89 0 0 1 .2.17zm6.93 1.71 21.52 37.27a7.09 7.09 0 0 0 -1.25 1.6 6.84 6.84 0 0 0 -.75 1.88h-43a6.84 6.84 0 0 0 -.75-1.88 7.26 7.26 0 0 0 -1.25-1.59l21.48-37.28a7.21 7.21 0 0 0 4 0zm3.28 54.09 16.38-9.46c0-.16-.09-.31-.13-.47h-43c0 .09 0 .19-.08.28l16.46 9.5a7.14 7.14 0 0 1 10.41.15z" fill="#e535ab" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1 @@
<svg id="Logos" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 254.6 150"><defs><style>.cls-1{fill:#253858;}.cls-2{fill:#2684ff;}.cls-3{fill:url(#New_Gradient_Swatch_8);}.cls-4{fill:url(#New_Gradient_Swatch_8-2);}</style><linearGradient id="New_Gradient_Swatch_8" x1="55.29" y1="50.91" x2="40.58" y2="66.08" gradientUnits="userSpaceOnUse"><stop offset="0.18" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient><linearGradient id="New_Gradient_Swatch_8-2" x1="39.15" y1="68.24" x2="22.14" y2="84.79" xlink:href="#New_Gradient_Swatch_8"/></defs><title>jira-logo-gradient-blue</title><path class="cls-1" d="M109.87,42.62h7.92V82.84c0,10.64-4.66,18-15.57,18a28.19,28.19,0,0,1-9.51-1.5V91.64a22.4,22.4,0,0,0,8.19,1.49c6.69,0,9-4,9-9.77Z"/><path class="cls-1" d="M132.23,39.54a4.94,4.94,0,0,1,5.27,5.28,5.28,5.28,0,1,1-10.55,0A4.94,4.94,0,0,1,132.23,39.54Zm-3.88,16.9h7.57v44h-7.57Z"/><path class="cls-1" d="M154.75,100.44h-7.39v-44h7.39v7.74c2.55-5.19,6.95-8.89,15.58-8.36v7.39c-9.68-1-15.58,1.94-15.58,11.27Z"/><path class="cls-1" d="M205.79,92.52c-2.81,5.8-8.09,8.8-14.87,8.8-11.7,0-17.6-9.95-17.6-22.88,0-12.41,6.16-22.88,18.48-22.88,6.42,0,11.35,2.9,14,8.62V56.44h7.57v44h-7.57Zm-12.85,1.76c6.78,0,12.85-4.32,12.85-14.08V76.68c0-9.77-5.54-14.08-12-14.08-8.53,0-12.93,5.63-12.93,15.84C180.89,89,185.11,94.28,192.94,94.28Z"/><path class="cls-2" d="M70.14,33.62H35.56A15.61,15.61,0,0,0,51.17,49.23h6.36v6.15A15.6,15.6,0,0,0,73.14,71V36.62A3,3,0,0,0,70.14,33.62Z"/><path class="cls-3" d="M53,50.85H18.44A15.61,15.61,0,0,0,34.05,66.46h6.37V72.6A15.61,15.61,0,0,0,56,88.21V53.85A3,3,0,0,0,53,50.85Z"/><path class="cls-4" d="M35.91,68.08H1.33a15.6,15.6,0,0,0,15.6,15.61H23.3v6.14a15.61,15.61,0,0,0,15.61,15.61V71.08A3,3,0,0,0,35.91,68.08Z"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 74 76" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
<title>Jira Software-blue</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="67.6800148%" y1="40.3276956%" x2="40.820818%" y2="81.6596195%" id="linearGradient-1">
<stop stop-color="#777" offset="18%"></stop>
<stop stop-color="#999" offset="100%"></stop>
</linearGradient>
<linearGradient x1="32.6559851%" y1="59.1664468%" x2="59.3430302%" y2="17.9899763%" id="linearGradient-2">
<stop stop-color="#777" offset="18%"></stop>
<stop stop-color="#999" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Jira-Software-blue" fill-rule="nonzero">
<path d="M72.4,35.76 L39.8,3.16 L36.64,0 L36.64,0 L12.1,24.54 L12.1,24.54 L0.88,35.76 C-0.289813512,36.9312702 -0.289813512,38.8287298 0.88,40 L23.3,62.42 L36.64,75.76 L61.18,51.22 L61.56,50.84 L72.4,40 C73.5698135,38.8287298 73.5698135,36.9312702 72.4,35.76 Z M36.64,49.08 L25.44,37.88 L36.64,26.68 L47.84,37.88 L36.64,49.08 Z" id="Shape" fill="#999"></path>
<path d="M36.64,26.68 C29.3070783,19.346212 29.2713402,7.46777926 36.56,0.09 L12.05,24.59 L25.39,37.93 L36.64,26.68 Z" id="Path" fill="url(#linearGradient-1)"></path>
<path d="M47.87,37.85 L36.64,49.08 C40.179363,52.6172581 42.1679334,57.4160731 42.1679334,62.42 C42.1679334,67.4239269 40.179363,72.2227419 36.64,75.76 L36.64,75.76 L61.21,51.19 L47.87,37.85 Z" id="Path" fill="url(#linearGradient-2)"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg width="2500" height="2500" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M256 236.394V19.607c0-8.894-5.923-16.4-14.037-18.8l-9.215 5.514-102.265 109.037-3.206 10.021-1.873 9.62 31.89 119.18 4.933 1.82h74.167c10.828 0 19.606-8.777 19.606-19.605" fill="#EA6618"/><path d="M0 19.606v216.787c0 6.705 3.367 12.62 8.5 16.155l6.287-3.01 108.246-115.894 4.244-8.265.159-7.99L97.976 5.306 93.513 0H19.606C8.778 0 0 8.778 0 19.606" fill="#d65813"/><path d="M127.277 125.38L241.963.806a19.595 19.595 0 0 0-5.57-.807H93.515l33.763 125.38z" fill="#e05e11"/><path d="M19.606 256h142.622l-34.951-130.621L8.499 252.549A19.511 19.511 0 0 0 19.606 256" fill="#de5c16"/><path d="M94.918 97.03h14.225c5.668 21.386 12.119 40.152 19.316 57.085 8.152-19.05 14.127-37.83 19.185-57.086h13.442c-6.02 23.926-15.868 48.04-27.132 72.93h-11.89c-10.82-23.586-20.03-47.837-27.146-72.93zm-46.92-37.055h31.63v135.637h-31.77v-10.456H67.33V70.152H47.998V59.975zm160.169 10.177h-19.332v115.004h19.47v10.456h-31.769V59.975h31.63v10.177z" fill="#FFF"/></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1 @@
<svg id="CMYK_-_square" data-name="CMYK - square" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 681.02 551.55"><defs><style>.cls-1{fill:#0097a0;}.cls-2{fill:#5bc6cc;}.cls-3{fill:#231f20;}</style></defs><title>NewRelic-logo-square</title><g id="outlines"><path class="cls-1" d="M692.8,220.54C660.86,73.7,484.77-12.68,299.47,27.61s-309.63,192-277.7,338.83,208,233.22,393.32,192.93S724.72,367.37,692.8,220.54ZM344.87,476.79c-103.41,0-187.2-83.82-187.2-187.22s83.8-187.19,187.2-187.19,187.2,83.81,187.2,187.19S448.25,476.79,344.87,476.79Z" transform="translate(-16.78 -17.71)"/><path class="cls-2" d="M391.53,57.56c-132.32,0-239.61,107.28-239.61,239.6S259.21,536.78,391.53,536.78,631.15,429.49,631.15,297.16,523.85,57.56,391.53,57.56ZM344.87,473.78c-101.75,0-184.19-82.47-184.19-184.21S243.12,105.4,344.87,105.4,529,187.85,529,289.57,446.58,473.78,344.87,473.78Z" transform="translate(-16.78 -17.71)"/><path class="cls-3" d="M278.93,271.2l-20.19-42.33c-4.82-10-9.77-21.36-11.46-26.7l-.39.39c.65,7.55.78,17.06.91,25l.52,43.63H233.61V181.08h16.93l21.88,44a164.17,164.17,0,0,1,9.25,23.18l.39-.39c-.39-4.56-1.3-17.45-1.3-25.66l-.26-41.15h14.2V271.2Z" transform="translate(-16.78 -17.71)"/><path class="cls-3" d="M321.51,242.16v1c0,9.12,3.39,18.75,16.28,18.75,6.12,0,11.46-2.21,16.41-6.51l5.6,8.73a35.59,35.59,0,0,1-23.7,8.73c-18.62,0-30.34-13.41-30.34-34.51,0-11.59,2.47-19.27,8.21-25.79,5.34-6.12,11.85-8.86,20.19-8.86a25.45,25.45,0,0,1,18.1,6.77c5.73,5.21,8.6,13.28,8.6,28.65v3Zm12.63-27.61c-8.07,0-12.5,6.38-12.5,17.06H346C346,220.93,341.31,214.55,334.15,214.55Z" transform="translate(-16.78 -17.71)"/><path class="cls-3" d="M437,271.46H423.61l-8.07-30.34c-2.08-7.81-4.3-18-4.3-18H411s-1,6.51-4.3,18.62l-7.94,29.69H385.32l-18-65.25,14.2-2,7.16,31.91c1.82,8.2,3.39,17.32,3.39,17.32h.39a178.91,178.91,0,0,1,3.78-17.71l8.47-30.47h14.07L426.22,235c2.74,10.68,4.17,18.75,4.17,18.75h.39s1.56-10,3.26-17.71l6.77-30.74h14.85Z" transform="translate(-16.78 -17.71)"/><path class="cls-3" d="M267.62,387.2l-7.81-13.94c-6.25-11.07-10.42-17.32-15.37-22.27a7.64,7.64,0,0,0-5.86-2.73V387.2H223.86V297.08h27.48c20.19,0,29.3,11.72,29.3,25.79,0,12.89-8.33,24.75-22.4,24.75,3.26,1.69,9.25,10.42,13.93,18l13.28,21.62Zm-20.84-78h-8.21v28.52h7.68c7.81,0,12-1,14.72-3.78,2.47-2.47,4-6.25,4-10.94C265,313.88,260.06,309.19,246.78,309.19Z" transform="translate(-16.78 -17.71)"/><path class="cls-3" d="M305.12,358.16v1c0,9.12,3.39,18.75,16.28,18.75,6.12,0,11.46-2.21,16.41-6.51l5.6,8.72a35.59,35.59,0,0,1-23.7,8.73c-18.62,0-30.34-13.41-30.34-34.51,0-11.59,2.47-19.28,8.21-25.79,5.34-6.12,11.85-8.86,20.19-8.86a25.45,25.45,0,0,1,18.1,6.77c5.73,5.21,8.6,13.28,8.6,28.65v3Zm12.63-27.61c-8.07,0-12.5,6.38-12.5,17.06H329.6C329.6,336.93,324.92,330.55,317.75,330.55Z" transform="translate(-16.78 -17.71)"/><path class="cls-3" d="M371.28,388.63c-14.46,0-14.46-13-14.46-18.62V313.88a106.72,106.72,0,0,0-1.3-19.27l14.72-3.26c1,4,1.17,9.51,1.17,18.1v55.87c0,8.86.39,10.29,1.43,11.85a4,4,0,0,0,4.69,1l2.34,8.86A22.44,22.44,0,0,1,371.28,388.63Z" transform="translate(-16.78 -17.71)"/><path class="cls-3" d="M396.15,311.53A9.34,9.34,0,0,1,386.9,302a9.44,9.44,0,1,1,9.25,9.51ZM389,387.2V322.34l14.46-2.6V387.2Z" transform="translate(-16.78 -17.71)"/><path class="cls-3" d="M444.46,388.89c-18,0-28-12.63-28-33.86,0-24,14.33-35.42,29-35.42,7.16,0,12.37,1.69,18.23,7.16l-7.16,9.51c-3.91-3.52-7.29-5.08-11.07-5.08a11.2,11.2,0,0,0-10.42,6.64c-2,4-2.73,10.16-2.73,18.36,0,9,1.43,14.72,4.43,18A11.58,11.58,0,0,0,445.5,378c4.56,0,9-2.21,13.28-6.51l6.77,8.72C459.57,386.16,453.32,388.89,444.46,388.89Z" transform="translate(-16.78 -17.71)"/><path class="cls-3" d="M477.78,388.64A9.67,9.67,0,1,1,487.4,379,9.63,9.63,0,0,1,477.78,388.64Zm0-17.42a7.78,7.78,0,1,0,7.44,7.75A7.55,7.55,0,0,0,477.78,371.22Zm1.9,13.1c-.42-.73-.6-1-1-1.79-1.07-2-1.4-2.5-1.79-2.65a.72.72,0,0,0-.34-.08v4.52H474.4V373.48h4a3,3,0,0,1,3.2,3.17,2.78,2.78,0,0,1-2.42,3,2.47,2.47,0,0,1,.44.47c.62.78,2.6,4.21,2.6,4.21Zm-1.14-8.94a4.35,4.35,0,0,0-1.22-.16h-.78v2.94h.73c.94,0,1.35-.1,1.64-.36a1.53,1.53,0,0,0,.42-1.09A1.28,1.28,0,0,0,478.53,375.38Z" transform="translate(-16.78 -17.71)"/></g></svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg"><rect fill="#fff" height="120" rx="6.01" width="120"/><g fill-rule="evenodd"><path d="m60.08 20.79-37.37 12.97 5.35 49.58 32.02 17.45z" fill="#412846"/><path d="m59.92 20.79 37.37 12.97-5.35 49.58-32.02 17.45z" fill="#4b314f"/><path d="m78.63 48.16a10.08 10.08 0 0 1 2.63 6.77 15 15 0 0 1 -2.65 8.25c1.36-1.06 2.93-3.34 4.71-6.82q1.16 10.59-8.58 16.08c2.07-.19 4.83-1.55 8.24-4.1q-5.46 13.17-20.1 13.89a24.42 24.42 0 0 1 -15.53-5.67 22.92 22.92 0 0 1 -8-11.39c-2.35-2.55-2.35-2.82-2.59-3.83s.15-1.3.83-2.29a3.7 3.7 0 0 0 .33-2.83 7.12 7.12 0 0 1 -1-3.76 3.68 3.68 0 0 1 1.65-2.61 8.47 8.47 0 0 0 2-2.11 10.37 10.37 0 0 0 .21-3.43c0-2 1.1-3.08 3.32-3.26 3.33-.26 5.22-2.77 6.26-3.91a4 4 0 0 1 3-1.13 6.34 6.34 0 0 1 4.94 2.07 20.12 20.12 0 0 1 11 2.87q7.97 4.71 8.7 10.18-.85 7.2-19.29-.37-9.65 2.73-9.49 11.84 0 8.35 8.07 12.14c-2.62-2.58-3.74-4.74-3.36-6.53q8.18 9.69 18.62 7.24a8.78 8.78 0 0 1 -7.32-3c4.7-.12 9.14-2.3 13.32-6.58a9.29 9.29 0 0 1 -7.61 2.19q10.86-8.51 7.69-19.9zm-13.15-.87a1.07 1.07 0 1 0 -1.06-1.07 1.06 1.06 0 0 0 1.06 1.07z" fill="#ba2bd2"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 52 59" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Group</title>
<g id="logos" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="1" transform="translate(-107.000000, -142.000000)" fill-rule="nonzero">
<g id="Group" transform="translate(107.000000, 142.000000)">
<path d="M44.2286654,29.5 L6.50039175,7.42000842 L6.50039175,51.5799916 L44.2286654,29.5 Z M49.3769757,24.9357962 C50.9991976,25.8727671 52,27.6142173 52,29.5 C52,31.3857827 50.9991976,33.1272329 49.3769757,34.0642038 L8.01498302,58.2754687 C4.63477932,60.2559134 0,57.9934848 0,53.7112649 L0,5.2887351 C0,1.00651517 4.63477932,-1.25591343 8.01498302,0.724531317 L49.3769757,24.9357962 Z" id="outline" fill="#394EFF"></path>
<path d="M29.4155818,28.4568548 L14.7929806,20.1454193 C14.2168086,19.8179252 13.4842425,20.0195184 13.1567483,20.5956904 C13.0540138,20.7764349 13,20.9807697 13,21.188671 L13,37.8115419 C13,38.4742836 13.5372583,39.0115419 14.2,39.0115419 C14.4079013,39.0115419 14.6122361,38.9575281 14.7929806,38.8547936 L29.4155818,30.5433581 C29.9917538,30.215864 30.193347,29.4832978 29.8658528,28.9071259 C29.7590506,28.7192249 29.6034827,28.563657 29.4155818,28.4568548 Z" id="inner-play" fill="#27A2A8"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg"><rect fill="#fff" height="120" rx="6.01" width="120"/><g fill="#764abc"><path d="m76.26 75.87a6 6 0 0 0 -.65-12h-.21a6 6 0 0 0 -5.79 6.22 6.16 6.16 0 0 0 1.71 4c-3.64 7.18-9.22 12.44-17.58 16.83a29 29 0 0 1 -17.48 3.33c-4.83-.64-8.58-2.79-10.94-6.33a15.74 15.74 0 0 1 -.86-16.62 25.18 25.18 0 0 1 7.29-8.58c-.43-1.39-1.07-3.75-1.39-5.47-15.55 11.22-13.94 26.45-9.23 33.63 3.54 5.37 10.73 8.69 18.66 8.69a26.22 26.22 0 0 0 6.44-.75 41.15 41.15 0 0 0 30.03-22.95z"/><path d="m95.13 62.57c-8.13-9.57-20.13-14.8-33.89-14.8h-1.71a5.9 5.9 0 0 0 -5.26-3.21h-.21a6 6 0 0 0 .21 12h.22a6 6 0 0 0 5.25-3.65h1.93a40.88 40.88 0 0 1 22.84 7 28.71 28.71 0 0 1 11.37 13.71 14.87 14.87 0 0 1 -.21 12.65 15.76 15.76 0 0 1 -14.67 8.8 27.55 27.55 0 0 1 -10.51-2.25c-1.18 1.07-3.32 2.78-4.82 3.86a33.16 33.16 0 0 0 13.8 3.32c10.3 0 17.91-5.68 20.81-11.37 3.11-6.22 2.89-16.94-5.15-26.06z"/><path d="m40.65 77.69a6 6 0 0 0 6 5.8h.21a6 6 0 0 0 -.21-12h-.22a1.8 1.8 0 0 0 -.75.11 39.29 39.29 0 0 1 -5.57-23.81 28.73 28.73 0 0 1 6.32-16.62c3.11-4 9.12-5.9 13.19-6 11.38-.24 16.21 13.92 16.53 19.6 1.39.32 3.75 1.07 5.36 1.61-1.29-17.38-12.01-26.38-22.31-26.38-9.65 0-18.55 7-22.09 17.27-4.93 13.73-1.71 26.91 4.29 37.31a4.85 4.85 0 0 0 -.75 3.11z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="rollbar-mark-color" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 304 240" style="enable-background:new 0 0 304 240;" xml:space="preserve">
<style type="text/css">
.st0{fill:#3A4757;}
.st1{fill:#F7941D;}
.st2{fill:#BFD730;}
.st3{fill:#00BAD9;}
</style>
<title>rollbar-logo-color-vertical</title>
<g id="icon">
<path class="st0" d="M303.8,239.1V25.7c-0.5-13.6-0.9-34.3-31.4-21.9C221.7,22.4,170.6,40.2,120.3,60C82.2,75,40.5,91.4,19,171.6
c-5.6,21-13.4,46.4-19,67.5h49.4c4.6-17,10.2-38.4,14.8-55.4c15.4-57.4,45.6-69.3,73.2-80.1C176.9,88,217,73.8,257,59.1v179.9
H303.8z"/>
<path class="st1" d="M119,124.5c-5,2.8-9.8,6.1-14.1,9.9c-14.9,13.3-23,32.2-28,51.1l-14.1,51.9H119V124.5z"/>
<path class="st2" d="M180.1,99.7c-12.7,4.7-25.3,9.6-38,14.5c-3.4,1.3-6.7,2.6-10,4v119.2H180L180.1,99.7z"/>
<path class="st3" d="M243.8,237.4v-161c-16.8,6.1-33.7,12.2-50.5,18.4v142.6H243.8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 256 238" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M159.580504,162.376777 L12.2929172,162.376777 C5.50108043,162.376777 0,156.875696 0,150.083859 C0,143.292023 5.50108043,137.790942 12.2929172,137.790942 L159.580504,137.790942 C166.372341,137.790942 171.873421,143.292023 171.873421,150.083859 C171.873421,156.875696 166.372341,162.376777 159.580504,162.376777 Z M243.707083,99.922611 L96.4256423,99.922611 C89.6338055,99.922611 84.1327251,94.4215306 84.1327251,87.6296938 C84.1327251,80.8378571 89.6338055,75.3367767 96.4256423,75.3367767 L243.707083,75.3367767 C250.49892,75.3367767 256,80.8378571 256,87.6296938 C256,94.4215306 250.49892,99.922611 243.707083,99.922611 Z M213.257527,40.7844597 C213.257527,47.5736504 207.753801,53.0773769 200.96461,53.0773769 C194.175419,53.0773769 188.671693,47.5736504 188.671693,40.7844597 C188.671693,33.995269 194.175419,28.4915426 200.96461,28.4915426 C207.753801,28.4915426 213.257527,33.995269 213.257527,40.7844597 Z M67.3252341,196.947533 C67.3252341,203.736724 61.8215076,209.24045 55.0323169,209.24045 C48.2431262,209.24045 42.7393998,203.736724 42.7393998,196.947533 C42.7393998,190.158342 48.2431262,184.654616 55.0323169,184.654616 C61.8215076,184.654616 67.3252341,190.158342 67.3252341,196.947533 Z" fill="#93C8A2"></path>
<path d="M127.932808,237.521926 C115.941219,237.521926 104.097068,235.740388 92.7320885,232.217245 C86.2514224,230.208999 82.6252498,223.327934 84.632237,216.846878 C86.6410739,210.335052 93.5399235,206.710545 100.005675,208.737812 C109.011654,211.532982 118.404655,212.945926 127.932808,212.945926 C169.623853,212.945926 205.822848,186.19215 218.014092,146.35329 C218.966942,143.237747 221.119645,140.629013 223.997689,139.102122 C226.875733,137.575232 230.242842,137.255537 233.356814,138.213507 C239.844067,140.179342 243.493147,147.059762 241.508883,153.540871 C226.138517,203.764865 180.494302,237.521926 127.932808,237.521926 Z M26.1087364,99.839808 C22.2118385,99.8403441 18.5457396,97.9921972 16.2285897,94.8590471 C13.9114398,91.7258969 13.2180139,87.6792824 14.359805,83.9534098 C29.7332433,33.7324874 75.3774578,-2.13162821e-14 127.932808,-2.13162821e-14 C139.933612,-2.13162821e-14 151.777764,1.78153731 163.133528,5.30775253 C166.246394,6.27212311 168.84862,8.43366553 170.367664,11.3167969 C171.886709,14.1999282 172.198119,17.5684397 171.23338,20.6811909 C170.271899,23.7958935 168.110641,26.4000467 165.226474,27.9190603 C162.342308,29.4380738 158.972271,29.747106 155.859942,28.7779708 C146.860106,26.0135163 137.460961,24.6005729 127.932808,24.6005729 C86.2509786,24.6005729 50.0458403,51.3543487 37.8545962,91.1932088 C36.2728154,96.3542766 31.5098278,99.8801084 26.111808,99.8858823 L26.1087364,99.839808 Z" fill="#43AF79"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 717.11 249.68"><title>sentry-logo-black</title><path d="M430.56,143.76,386.07,86.33H375v77h11.22v-59l45.74,59h9.82v-77H430.56Zm-112-14.27H358.4v-10H318.52V96.31h45v-10H307.07v77h57v-10H318.52Zm-46.84-9.78c-15.57-3.72-19.83-6.69-19.83-13.84,0-6.46,5.71-10.81,14.22-10.81,7.09,0,14.07,2.51,21.3,7.67l6.06-8.54c-8-6.13-16.65-9-27.13-9-15.25,0-25.89,9-25.89,21.92,0,13.84,9,18.63,25.5,22.63,14.51,3.35,18.93,6.5,18.93,13.5s-6,11.38-15.35,11.38c-9.07,0-16.81-3-25-9.82l-6.79,8.08a47.82,47.82,0,0,0,31.41,11.6c16.49,0,27.14-8.87,27.14-22.6C296.27,130.23,289.38,124,271.68,119.71Zm373.9-33.37-23.19,36.31-23-36.31H586l30.51,46.54v30.47h11.56V132.53l30.5-46.19ZM450.87,96.76H476.1v66.58h11.57V96.76h25.23V86.33h-62ZM566.4,133.28c11.64-3.21,18-11.37,18-23,0-14.78-10.84-24-28.28-24H522v77h11.45V135.62h19.42l19.54,27.72h13.37l-21.1-29.58Zm-33-7.52V96.53H555c11.27,0,17.74,5.31,17.74,14.56,0,8.91-6.92,14.67-17.62,14.67ZM144.9,65.43a13.75,13.75,0,0,0-23.81,0l-19.6,33.95,5,2.87a96.14,96.14,0,0,1,47.83,77.4H140.56a82.4,82.4,0,0,0-41-65.54l-5-2.86L76.3,143l5,2.87a46.35,46.35,0,0,1,22.46,33.78H72.33a2.27,2.27,0,0,1-2-3.41l8.76-15.17a31.87,31.87,0,0,0-10-5.71L60.42,170.5a13.75,13.75,0,0,0,11.91,20.62h43.25v-5.73A57.16,57.16,0,0,0,91.84,139l6.88-11.92a70.93,70.93,0,0,1,30.56,58.26v5.74h36.65v-5.73A107.62,107.62,0,0,0,117.09,95.3L131,71.17a2.27,2.27,0,0,1,3.93,0l60.66,105.07a2.27,2.27,0,0,1-2,3.41H179.4c.18,3.83.2,7.66,0,11.48h14.24a13.75,13.75,0,0,0,11.91-20.62Z" style="fill:#221f20"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,6 @@
<svg viewBox="0 0 150 134" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Untitled</title>
<g id="sentry" fill="#000000" fill-rule="nonzero">
<path style="fill:#221f20" d="M86.9,7.43 C84.4430551,3.1785152 79.9053667,0.559932776 74.995,0.559932776 C70.0846333,0.559932776 65.5469449,3.1785152 63.09,7.43 L43.49,41.38 L48.49,44.25 C76.4054109,60.3953737 94.3667995,89.4610537 96.32,121.65 L82.56,121.65 C80.6256171,94.350123 65.2628284,69.7921433 41.56,56.11 L36.56,53.25 L18.3,85 L23.3,87.87 C35.5781336,95.0821178 43.8597493,107.537727 45.76,121.65 L14.33,121.65 C13.5113725,121.663415 12.7489511,121.23496 12.3347402,120.52873 C11.9205293,119.8225 11.918718,118.947939 12.33,118.24 L21.09,103.07 C18.1310309,100.575254 14.7423935,98.6403422 11.09,97.36 L2.42,112.5 C-0.033833822,116.754289 -0.0322538222,121.994373 2.42414508,126.247181 C4.88054397,130.49999 9.41876057,133.119738 14.33,133.12 L57.58,133.12 L57.58,127.39 C57.585764,109.009083 48.7518498,91.7468551 33.84,81 L40.72,69.08 C59.8414525,82.3159451 71.2600253,104.084468 71.28,127.34 L71.28,133.08 L107.93,133.08 L107.93,127.35 C107.896313,91.0129684 89.5283621,57.1465894 59.09,37.3 L73,13.17 C73.4057253,12.4685156 74.1546338,12.0365188 74.965,12.0365188 C75.7753662,12.0365188 76.5242747,12.4685156 76.93,13.17 L137.59,118.24 C138.001282,118.947939 137.999471,119.8225 137.58526,120.52873 C137.171049,121.23496 136.408627,121.663415 135.59,121.65 L121.4,121.65 C121.58,125.48 121.6,129.31 121.4,133.13 L135.64,133.13 C140.551239,133.13 145.089456,130.50999 147.545855,126.257181 C150.002254,122.004373 150.003834,116.764289 147.55,112.51 L86.9,7.43 Z" id="Path"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-slack" viewBox="0 0 16 16">
<path d="M3.362 10.11c0 .926-.756 1.681-1.681 1.681S0 11.036 0 10.111C0 9.186.756 8.43 1.68 8.43h1.682v1.68zm.846 0c0-.924.756-1.68 1.681-1.68s1.681.756 1.681 1.68v4.21c0 .924-.756 1.68-1.68 1.68a1.685 1.685 0 0 1-1.682-1.68v-4.21zM5.89 3.362c-.926 0-1.682-.756-1.682-1.681S4.964 0 5.89 0s1.68.756 1.68 1.68v1.682H5.89zm0 .846c.924 0 1.68.756 1.68 1.681S6.814 7.57 5.89 7.57H1.68C.757 7.57 0 6.814 0 5.89c0-.926.756-1.682 1.68-1.682h4.21zm6.749 1.682c0-.926.755-1.682 1.68-1.682.925 0 1.681.756 1.681 1.681s-.756 1.681-1.68 1.681h-1.681V5.89zm-.848 0c0 .924-.755 1.68-1.68 1.68A1.685 1.685 0 0 1 8.43 5.89V1.68C8.43.757 9.186 0 10.11 0c.926 0 1.681.756 1.681 1.68v4.21zm-1.681 6.748c.926 0 1.682.756 1.682 1.681S11.036 16 10.11 16s-1.681-.756-1.681-1.68v-1.682h1.68zm0-.847c-.924 0-1.68-.755-1.68-1.68 0-.925.756-1.681 1.68-1.681h4.21c.924 0 1.68.756 1.68 1.68 0 .926-.756 1.681-1.68 1.681h-4.21z"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 51 51">
<g fill="none">
<path fill="#E01E5A" d="M10.9228346 5.62204724C10.9228346 8.55354331 8.55354331 10.9228346 5.62204724 10.9228346 2.69055118 10.9228346.321259843 8.55354331.321259843 5.62204724.321259843 2.69055118 2.69055118.321259843 5.62204724.321259843L10.9228346.321259843 10.9228346 5.62204724zM13.5732283 5.62204724C13.5732283 2.69055118 15.9425197.321259843 18.8740157.321259843 21.8055118.321259843 24.1748031 2.69055118 24.1748031 5.62204724L24.1748031 18.8740157C24.1748031 21.8055118 21.8055118 24.1748031 18.8740157 24.1748031 15.9425197 24.1748031 13.5732283 21.8055118 13.5732283 18.8740157L13.5732283 5.62204724z" transform="translate(0 26.504)"/>
<path fill="#36C5F0" d="M18.8740157 10.8425197C15.9425197 10.8425197 13.5732283 8.47322835 13.5732283 5.54173228 13.5732283 2.61023622 15.9425197.240944882 18.8740157.240944882 21.8055118.240944882 24.1748031 2.61023622 24.1748031 5.54173228L24.1748031 10.8425197 18.8740157 10.8425197zM18.8740157 13.5330709C21.8055118 13.5330709 24.1748031 15.9023622 24.1748031 18.8338583 24.1748031 21.7653543 21.8055118 24.1346457 18.8740157 24.1346457L5.58188976 24.1346457C2.6503937 24.1346457.281102362 21.7653543.281102362 18.8338583.281102362 15.9023622 2.6503937 13.5330709 5.58188976 13.5330709L18.8740157 13.5330709z"/>
<path fill="#2EB67D" d="M13.6133858 18.8338583C13.6133858 15.9023622 15.9826772 13.5330709 18.9141732 13.5330709 21.8456693 13.5330709 24.2149606 15.9023622 24.2149606 18.8338583 24.2149606 21.7653543 21.8456693 24.1346457 18.9141732 24.1346457L13.6133858 24.1346457 13.6133858 18.8338583zM10.9629921 18.8338583C10.9629921 21.7653543 8.59370079 24.1346457 5.66220472 24.1346457 2.73070866 24.1346457.361417323 21.7653543.361417323 18.8338583L.361417323 5.54173228C.361417323 2.61023622 2.73070866.240944882 5.66220472.240944882 8.59370079.240944882 10.9629921 2.61023622 10.9629921 5.54173228L10.9629921 18.8338583z" transform="translate(26.504)"/>
<path fill="#ECB22E" d="M5.66220472 13.5732283C8.59370079 13.5732283 10.9629921 15.9425197 10.9629921 18.8740157 10.9629921 21.8055118 8.59370079 24.1748031 5.66220472 24.1748031 2.73070866 24.1748031.361417323 21.8055118.361417323 18.8740157L.361417323 13.5732283 5.66220472 13.5732283zM5.66220472 10.9228346C2.73070866 10.9228346.361417323 8.55354331.361417323 5.62204724.361417323 2.69055118 2.73070866.321259843 5.66220472.321259843L18.9543307.321259843C21.8858268.321259843 24.2551181 2.69055118 24.2551181 5.62204724 24.2551181 8.55354331 21.8858268 10.9228346 18.9543307 10.9228346L5.66220472 10.9228346z" transform="translate(26.504 26.504)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 65 57" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" font-family="Roboto" font-size="14px" text-anchor="middle">
<defs>
<style type="text/css"></style>
</defs>
<use xlink:href="#A" x=".5" y=".5"></use>
<symbol id="A" overflow="visible"><g stroke="none" fill-rule="nonzero"><path d="M47.83 55.172l-16-29.834-15.984 29.84z" fill="#009245"></path><path d="M31.83 27.617l-16 27.565L0 27.617z" fill="#006837"></path><path d="M0 27.627L15.97.01h31.942L31.905 27.627z" fill="#39b54a"></path><path d="M47.83 55.172L31.904 27.618 47.91 0 64 27.585z" fill="#8cc63f"></path></g></symbol>
</svg>

After

Width:  |  Height:  |  Size: 750 B

View file

@ -0,0 +1,4 @@
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<circle cx="512" cy="512" r="512" style="fill:#009"/>
<path d="M352.9 361.4c-12.7-2.7-21.8-5.1-26.9-6.3-6.3-1.8-11.2-4.2-13.6-7.6v-13.9c4.2-6.6 14.5-10.9 28.7-10.9 19.6 0 33.2 4.5 54.7 21.5l25.1-30.2c-25.4-21.2-47.1-28.7-78.6-28.7-36.3 0-61.3 14.8-71.3 36v45.9c7.9 15.4 26 24.2 62.2 32 13 3 21.8 5.1 26.9 6 6.6 2.1 12.7 4.8 16 9.7v16c-4.5 7.3-15.4 11.2-29.6 11.2-10.3.3-20.2-1.8-29.6-5.7-9.1-3.6-19.6-10.6-32.9-21.5l-26.9 29.9c29 25.7 53.5 34.4 88.2 34.4 37.2 0 63.5-14.2 73.4-36.9v-46.2c-8.4-17.2-28.7-26.2-65.8-34.7zm401.5-70.1v182.5h-42.6v-19.3c-9.7 15.7-27.5 24.8-53.5 24.8-43.2 0-64.7-22.4-64.7-58v-130h46.5v117.5c0 19 10.3 30.2 30.2 30.2 23.3 0 37.5-13.3 37.5-37.8v-110h46.6zM531.8 606.9v127.5h-46.5V620.8c0-21.8-8.8-35.1-29-35.1s-32 15.1-32 36.3v112.4h-46.5V620.8c0-23.3-9.7-35.1-29-35.1-20.5 0-32 15.1-32 36.3v112.4h-46.5V551.9h43.2v20.2c10.6-17.5 28.1-26.3 52-26.3 23.3 0 40.8 9.7 50.8 26.9 12.4-17.8 30.8-26.9 54.7-26.9 38.8.1 60.8 23.4 60.8 61.1zm139.8-62.8c-42 0-74.9 22.7-88.5 58.9v76.8c13.3 36.3 46.5 58.9 88.5 58.9s74.6-22.7 87.9-58.6v-77.4c-13.5-35.9-46.5-58.6-87.9-58.6zm43 124.2c-7.3 19.3-22.7 30.8-43.2 30.8-20.2 0-36.3-11.5-43.5-30.8v-53.8c7.3-19.3 22.7-31.1 43.5-31.1 20.5 0 35.7 11.8 42.9 31.1l.3 53.8z" style="fill:#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1 @@
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg"><rect fill="#fff" height="120" rx="6.01" width="120"/><path d="m87.83 20h18.55l-46.38 80-46.38-80h35.48l10.9 18.55 10.67-18.55z" fill="#41b883"/><path d="m13.62 20 46.38 80 46.38-80h-18.55l-27.83 48-28.06-48z" fill="#41b883"/><path d="m31.94 20 28.06 48.23 27.83-48.23h-17.16l-10.67 18.55-10.9-18.55z" fill="#35495e"/></svg>

After

Width:  |  Height:  |  Size: 386 B

View file

@ -50,7 +50,7 @@ const integrationsRoute = client(CLIENT_TABS.INTEGRATIONS);
const AlertForm = props => {
const { instance, slackChannels, webhooks, loading, onDelete, deleting, triggerOptions, metricId, style={ width: '580px', height: '100vh' } } = props;
const write = ({ target: { value, name } }) => props.edit({ [ name ]: value })
const writeOption = (e, { name, value }) => props.edit({ [ name ]: value });
const writeOption = (e, { name, value }) => props.edit({ [ name ]: value.value });
const onChangeCheck = ({ target: { checked, name }}) => props.edit({ [ name ]: checked })
// const onChangeOption = ({ checked, name }) => props.edit({ [ name ]: checked })
// const onChangeCheck = (e) => { console.log(e) }
@ -96,7 +96,7 @@ const AlertForm = props => {
primary
name="detectionMethod"
className="my-3"
onSelect={ writeOption }
onSelect={ (e, { name, value }) => props.edit({ [ name ]: value }) }
value={{ value: instance.detectionMethod }}
list={ [
{ name: 'Threshold', value: 'threshold' },
@ -144,7 +144,7 @@ const AlertForm = props => {
name="left"
value={ triggerOptions.find(i => i.value === instance.query.left) }
// onChange={ writeQueryOption }
onChange={ ({ value }) => writeQueryOption(null, { name: 'left', value }) }
onChange={ ({ value }) => writeQueryOption(null, { name: 'left', value: value.value }) }
/>
</div>
@ -157,29 +157,32 @@ const AlertForm = props => {
name="operator"
defaultValue={ instance.query.operator }
// onChange={ writeQueryOption }
onChange={ ({ value }) => writeQueryOption(null, { name: 'operator', value }) }
onChange={ ({ value }) => writeQueryOption(null, { name: 'operator', value: value.value }) }
/>
{ unit && (
<Input
className="px-4"
style={{ marginRight: '31px'}}
label={{ basic: true, content: unit }}
labelPosition='right'
name="right"
value={ instance.query.right }
onChange={ writeQuery }
placeholder="E.g. 3"
/>
<>
<Input
className="px-4"
style={{ marginRight: '31px'}}
// label={{ basic: true, content: unit }}
// labelPosition='right'
name="right"
value={ instance.query.right }
onChange={ writeQuery }
placeholder="E.g. 3"
/>
<span className="ml-2">{'test'}</span>
</>
)}
{ !unit && (
<Input
wrapperClassName="ml-2"
// className="pl-4"
name="right"
value={ instance.query.right }
onChange={ writeQuery }
placeholder="Specify Value"
/>
<Input
wrapperClassName="ml-2"
// className="pl-4"
name="right"
value={ instance.query.right }
onChange={ writeQuery }
placeholder="Specify Value"
/>
)}
</div>
</div>
@ -309,7 +312,7 @@ const AlertForm = props => {
{instance.exists() ? 'Update' : 'Create'}
</Button>
<div className="mx-1" />
<Button basic onClick={props.onClose}>Cancel</Button>
<Button onClick={props.onClose}>Cancel</Button>
</div>
<div>
{instance.exists() && (

View file

@ -21,7 +21,6 @@ const AlertsList = props => {
<div className="mb-3 w-full px-3">
<Input
name="searchQuery"
fluid
placeholder="Search by Name or Metric"
onChange={({ target: { value } }) => setQuery(value)}
/>

View file

@ -16,14 +16,11 @@ interface Props {
fetchList: any;
}
function Notifications(props: Props) {
// const { notifications } = props;
const { showModal } = useModal();
// const unReadNotificationsCount = notifications.filter(({viewed}: any) => !viewed).size
const { notificationStore } = useStore();
const count = useObserver(() => notificationStore.notificationsCount);
useEffect(() => {
notificationStore.fetchNotificationsCount();
const interval = setInterval(() => {
notificationStore.fetchNotificationsCount()
}, AUTOREFRESH_INTERVAL);

View file

@ -29,14 +29,14 @@ function ChatControls({ stream, endCall, videoEnabled, setVideoEnabled } : Props
<div className={cn(stl.controls, "flex items-center w-full justify-start bottom-0 px-2")}>
<div className="flex items-center">
<div className={cn(stl.btnWrapper, { [stl.disabled]: audioEnabled})}>
<Button varient="text" onClick={toggleAudio} hover>
<Button variant="text" onClick={toggleAudio} hover>
<Icon name={audioEnabled ? 'mic' : 'mic-mute'} size="16" />
<span className={cn("ml-1 color-gray-medium text-sm", { 'color-red' : audioEnabled })}>{audioEnabled ? 'Mute' : 'Unmute'}</span>
</Button>
</div>
<div className={cn(stl.btnWrapper, { [stl.disabled]: videoEnabled})}>
<Button varient="text" onClick={toggleVideo} hover>
<Button variant="text" onClick={toggleVideo} hover>
<Icon name={ videoEnabled ? 'camera-video' : 'camera-video-off' } size="16" />
<span className={cn("ml-1 color-gray-medium text-sm", { 'color-red' : videoEnabled })}>{videoEnabled ? 'Stop Video' : 'Start Video'}</span>
</Button>

View file

@ -1,7 +1,6 @@
//@ts-nocheck
import React, { useState, FC, useEffect } from 'react'
import VideoContainer from '../components/VideoContainer'
import { Icon, Popup, Button } from 'UI'
import cn from 'classnames'
import Counter from 'App/components/shared/SessionItem/Counter'
import stl from './chatWindow.module.css'

View file

@ -6,19 +6,14 @@ import {
fetchFavoriteList as fetchFavoriteSessionList
} from 'Duck/sessions';
import { applyFilter, clearEvents, addAttribute } from 'Duck/filters';
import { fetchList as fetchFunnelsList } from 'Duck/funnels';
import { KEYS } from 'Types/filter/customFilter';
import SessionList from './SessionList';
import stl from './bugFinder.module.css';
import withLocationHandlers from "HOCs/withLocationHandlers";
import { fetch as fetchFilterVariables } from 'Duck/sources';
import { fetchSources } from 'Duck/customField';
import { setFunnelPage } from 'Duck/sessions';
import { setActiveTab } from 'Duck/search';
import SessionsMenu from './SessionsMenu/SessionsMenu';
import { LAST_7_DAYS } from 'Types/app/period';
import { resetFunnel } from 'Duck/funnels';
import { resetFunnelFilters } from 'Duck/funnelFilters'
import NoSessionsMessage from 'Shared/NoSessionsMessage';
import SessionSearch from 'Shared/SessionSearch';
import MainSearchBar from 'Shared/MainSearchBar';
@ -65,10 +60,6 @@ const allowedQueryKeys = [
fetchSources,
clearEvents,
setActiveTab,
fetchFunnelsList,
resetFunnel,
resetFunnelFilters,
setFunnelPage,
clearSearch,
fetchSessions,
})
@ -94,9 +85,6 @@ export default class BugFinder extends React.PureComponent {
if (props.sessions.size === 0) {
props.fetchSessions();
}
props.resetFunnel();
props.resetFunnelFilters();
props.fetchFunnelsList(LAST_7_DAYS)
const queryFilter = this.props.query.all(allowedQueryKeys);
if (queryFilter.hasOwnProperty('userId')) {
@ -104,10 +92,6 @@ export default class BugFinder extends React.PureComponent {
}
}
componentDidMount() {
this.props.setFunnelPage(false);
}
toggleRehydratePanel = () => {
this.setState({ showRehydratePanel: !this.state.showRehydratePanel })
}

View file

@ -95,42 +95,48 @@ export default class SessionList extends React.PureComponent {
const hasUserFilter = _filterKeys.includes(FilterKey.USERID) || _filterKeys.includes(FilterKey.USERANONYMOUSID);
return (
<NoContent
title={<div className="flex items-center justify-center flex-col">
<AnimatedSVG name={ICONS.NO_RESULTS} size="170" />
{this.getNoContentMessage(activeTab)}
</div>}
// subtext="Please try changing your search parameters."
// animatedIcon="no-results"
show={ !loading && list.size === 0}
subtext={
<div>
<div>Please try changing your search parameters.</div>
</div>
}
>
<Loader loading={ loading }>
{ list.map(session => (
<SessionItem
key={ session.sessionId }
session={ session }
hasUserFilter={hasUserFilter}
onUserClick={this.onUserClick}
metaList={metaList}
lastPlayedSessionId={lastPlayedSessionId}
/>
))}
</Loader>
<div className="w-full flex items-center justify-center py-6">
<Pagination
page={currentPage}
totalPages={Math.ceil(total / PER_PAGE)}
onPageChange={(page) => this.props.updateCurrentPage(page)}
limit={PER_PAGE}
debounceRequest={1000}
/>
<div className="bg-white p-3 rounded border">
<NoContent
title={<div className="flex items-center justify-center flex-col">
<AnimatedSVG name={ICONS.NO_RESULTS} size="170" />
{this.getNoContentMessage(activeTab)}
</div>}
// subtext="Please try changing your search parameters."
// animatedIcon="no-results"
show={ !loading && list.size === 0}
subtext={
<div>
<div>Please try changing your search parameters.</div>
</div>
}
>
<Loader loading={ loading }>
{ list.map(session => (
<>
<SessionItem
key={ session.sessionId }
session={ session }
hasUserFilter={hasUserFilter}
onUserClick={this.onUserClick}
metaList={metaList}
lastPlayedSessionId={lastPlayedSessionId}
/>
<div className="border-b" />
</>
))}
</Loader>
<div className="w-full flex items-center justify-center py-6">
<Pagination
page={currentPage}
totalPages={Math.ceil(total / PER_PAGE)}
onPageChange={(page) => this.props.updateCurrentPage(page)}
limit={PER_PAGE}
debounceRequest={1000}
/>
</div>
</NoContent>
</div>
</NoContent>
);
}

View file

@ -2,7 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import { tokenRE } from 'Types/integrations/bugsnagConfig';
import { edit } from 'Duck/integrations/actions';
import { Dropdown } from 'UI';
import Select from 'Shared/Select';
import { withRequest } from 'HOCs';
@connect(state => ({
@ -50,7 +50,7 @@ export default class ProjectListDropdown extends React.PureComponent {
this.fetchProjectList();
}
}
onChange = (e, target) => {
onChange = (target) => {
if (typeof this.props.onChange === 'function') {
this.props.onChange({ target });
}
@ -65,11 +65,11 @@ export default class ProjectListDropdown extends React.PureComponent {
} = this.props;
const options = projects.map(({ name, id }) => ({ text: name, value: id }));
return (
<Dropdown
selection
<Select
// selection
options={ options }
name={ name }
value={ value }
value={ options.find(o => o.value === value) }
placeholder={ placeholder }
onChange={ this.onChange }
loading={ loading }

View file

@ -2,7 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import { ACCESS_KEY_ID_LENGTH, SECRET_ACCESS_KEY_LENGTH } from 'Types/integrations/cloudwatchConfig';
import { edit } from 'Duck/integrations/actions';
import { Dropdown } from 'UI';
import Select from 'Shared/Select';
import { withRequest } from 'HOCs';
@connect(state => ({
@ -48,7 +48,7 @@ export default class LogGroupDropdown extends React.PureComponent {
this.fetchLogGroups();
}
}
onChange = (e, target) => {
onChange = (target) => {
if (typeof this.props.onChange === 'function') {
this.props.onChange({ target });
}
@ -63,11 +63,11 @@ export default class LogGroupDropdown extends React.PureComponent {
} = this.props;
const options = values.map(g => ({ text: g, value: g }));
return (
<Dropdown
selection
<Select
// selection
options={ options }
name={ name }
value={ value }
value={ options.find(o => o.value === value) }
placeholder={ placeholder }
onChange={ this.onChange }
loading={ loading }

View file

@ -18,7 +18,7 @@ const IntegrationItem = ({
<Icon name="check" size="14" color="white" />
</div>
)}
<Icon name={ icon } size="40" />
<img className="h-12 w-12" src={'/assets/' + icon + '.svg'} alt="integration" />
<h4 className="my-2">{ title }</h4>
</div>
)

View file

@ -4,7 +4,8 @@ import cn from 'classnames';
import withPageTitle from 'HOCs/withPageTitle';
import {
Form, IconButton, SlideModal, Input, Button, Loader,
NoContent, Popup, CopyButton, Dropdown } from 'UI';
NoContent, Popup, CopyButton } from 'UI';
import Select from 'Shared/Select';
import { init, save, edit, remove as deleteMember, fetchList, generateInviteLink } from 'Duck/member';
import { fetchList as fetchRoles } from 'Duck/roles';
import styles from './manageUsers.module.css';
@ -39,7 +40,7 @@ class ManageUsers extends React.PureComponent {
state = { showModal: false, remaining: this.props.account.limits.teamMember.remaining, invited: false }
// writeOption = (e, { name, value }) => this.props.edit({ [ name ]: value });
onChange = (e, { name, value }) => this.props.edit({ [ name ]: value });
onChange = ({ name, value }) => this.props.edit({ [ name ]: value.value });
onChangeCheckbox = ({ target: { checked, name } }) => this.props.edit({ [ name ]: checked });
setFocus = () => this.focusElement && this.focusElement.focus();
closeModal = () => this.setState({ showModal: false });
@ -138,12 +139,12 @@ class ManageUsers extends React.PureComponent {
{ isEnterprise && (
<Form.Field>
<label htmlFor="role">{ 'Role' }</label>
<Dropdown
<Select
placeholder="Role"
selection
options={ roles }
name="roleId"
value={ member.roleId }
value={ roles.find(r => r.value === member.roleId) }
onChange={ this.onChange }
/>
</Form.Field>

View file

@ -127,7 +127,7 @@ const RoleForm = (props: Props) => {
isSearchable
name="permissions"
options={ permissions }
onChange={ ({ value }: any) => writeOption({ name: 'permissions', value }) }
onChange={ ({ value }: any) => writeOption({ name: 'permissions', value: value.value }) }
value={null}
/>
{ role.permissions.size > 0 && (

View file

@ -0,0 +1,29 @@
import React from 'react';
import { Popup, IconButton } from 'UI';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
const PERMISSION_WARNING = 'You dont have the permissions to perform this action.';
const LIMIT_WARNING = 'You have reached site limit.';
function AddProjectButton({ isAdmin = false, onClick }: any ) {
const { userStore } = useStore();
const limtis = useObserver(() => userStore.limits);
const canAddProject = useObserver(() => isAdmin && (limtis.projects === -1 || limtis.projects > 0));
return (
<Popup
content={ `${ !isAdmin ? PERMISSION_WARNING : (!canAddProject ? LIMIT_WARNING : 'Add a Project') }` }
>
<IconButton
id="add-button"
disabled={ !canAddProject || !isAdmin }
circle
icon="plus"
outline
onClick={ onClick }
/>
</Popup>
);
}
export default AddProjectButton;

View file

@ -0,0 +1 @@
export { default } from './AddUserButton';

View file

@ -2,7 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import cn from 'classnames';
import withPageTitle from 'HOCs/withPageTitle';
import { Loader, SlideModal, IconButton, Icon, Button, Popup, TextLink } from 'UI';
import { Loader, SlideModal, Icon, Button, Popup, TextLink } from 'UI';
import { init, remove, fetchGDPR } from 'Duck/site';
import { RED, YELLOW, GREEN, STATUS_COLOR_MAP } from 'Types/site';
import stl from './sites.module.css';
@ -10,8 +10,9 @@ import NewSiteForm from './NewSiteForm';
import GDPRForm from './GDPRForm';
import TrackingCodeModal from 'Shared/TrackingCodeModal';
import BlockedIps from './BlockedIps';
import { confirm } from 'UI';
import { confirm, PageTitle } from 'UI';
import SiteSearch from './SiteSearch';
import AddProjectButton from './AddProjectButton';
const STATUS_MESSAGE_MAP = {
[ RED ]: ' There seems to be an issue (please verify your installation)',
@ -19,9 +20,6 @@ const STATUS_MESSAGE_MAP = {
[ GREEN ]: 'All good!',
};
const PERMISSION_WARNING = 'You dont have the permissions to perform this action.';
const LIMIT_WARNING = 'You have reached site limit.';
const BLOCKED_IPS = 'BLOCKED_IPS';
const NONE = 'NONE';
@ -143,21 +141,14 @@ class Sites extends React.PureComponent {
/>
<div className={ stl.wrapper }>
<div className={ stl.tabHeader }>
<h3 className={ cn(stl.tabTitle, "text-2xl") }>{ 'Projects' }</h3>
<Popup
disabled={ canAddSites }
content={ `${ !isAdmin ? PERMISSION_WARNING : LIMIT_WARNING }` }
>
<div>
<IconButton
disabled={ !canAddSites }
circle
icon="plus"
outline
onClick={ this.showNewSiteForm }
/>
</div>
</Popup>
{/* <h3 className={ cn(stl.tabTitle, "text-2xl") }>{ 'Projects' }</h3> */}
<PageTitle
title={<div className="mr-4">Projects</div>}
actionButton={(
<AddProjectButton isAdmin={isAdmin} onClick={this.showNewSiteForm} />
)}
/>
<div className="flex ml-auto items-center">
<TextLink

View file

@ -1,15 +1,14 @@
import React, { useEffect } from 'react';
import UserList from './components/UserList';
import { PageTitle, Popup, IconButton } from 'UI';
import { PageTitle } from 'UI';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
import UserSearch from './components/UserSearch';
import { useModal } from 'App/components/Modal';
import UserForm from './components/UserForm';
import { connect } from 'react-redux';
import AddUserButton from './components/AddUserButton';
const PERMISSION_WARNING = 'You dont have the permissions to perform this action.';
const LIMIT_WARNING = 'You have reached users limit.';
interface Props {
account: any;
isEnterprise: boolean;
@ -43,22 +42,7 @@ function UsersView(props: Props) {
<PageTitle
title={<div>Team <span className="color-gray-medium">{userCount}</span></div>}
actionButton={(
<Popup
content={ `${ !isAdmin ? PERMISSION_WARNING : (reachedLimit ? LIMIT_WARNING : 'Add team member') }` }
size="tiny"
inverted
position="top left"
>
<IconButton
id="add-button"
disabled={ reachedLimit || !isAdmin }
circle
icon="plus"
outline
className="ml-3"
onClick={ () => editHandler(null) }
/>
</Popup>
<AddUserButton isAdmin={isAdmin} onClick={() => editHandler(null)} />
)}
/>
<div>

View file

@ -0,0 +1,30 @@
import React from 'react';
import { Popup, IconButton } from 'UI';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
const PERMISSION_WARNING = 'You dont have the permissions to perform this action.';
const LIMIT_WARNING = 'You have reached users limit.';
function AddUserButton({ isAdmin = false, onClick }: any ) {
const { userStore } = useStore();
const limtis = useObserver(() => userStore.limits);
const cannAddUser = useObserver(() => isAdmin && (limtis.teamMember === -1 || limtis.teamMember > 0));
return (
<Popup
content={ `${ !isAdmin ? PERMISSION_WARNING : (!cannAddUser ? LIMIT_WARNING : 'Add team member') }` }
>
<IconButton
id="add-button"
disabled={ !cannAddUser || !isAdmin }
circle
icon="plus"
outline
onClick={ onClick }
className="ml-4"
/>
</Popup>
);
}
export default AddUserButton;

View file

@ -0,0 +1 @@
export { default } from './AddUserButton';

View file

@ -27,6 +27,7 @@ function UserForm(props: Props) {
const onSave = () => {
userStore.saveUser(user).then(() => {
hideModal();
userStore.fetchLimits();
});
}
@ -42,6 +43,7 @@ function UserForm(props: Props) {
})) {
userStore.deleteUser(user.userId).then(() => {
hideModal();
userStore.fetchLimits();
});
}
}

View file

@ -47,29 +47,32 @@ function UserListItem(props: Props) {
<div className="col-span-2 justify-self-end invisible group-hover:visible">
<div className="grid grid-cols-2 gap-3 items-center justify-end">
{!user.isJoined && user.invitationLink ? (
<Popup
delay={500}
content="Copy Invite Code"
hideOnClick={true}
>
<button className='' onClick={copyInviteCode}>
<Icon name="link-45deg" size="16" color="teal"/>
</button>
</Popup>
) : <div/>}
{!user.isJoined && user.isExpiredInvite && (
<Popup
delay={500}
arrow
content="Generate Invite"
hideOnClick={true}
>
<button className='' onClick={generateInvite}>
<Icon name="link-45deg" size="16" color="red"/>
</button>
</Popup>
)}
<div>
{!user.isJoined && user.invitationLink && !user.isExpiredInvite && (
<Popup
delay={500}
content="Copy Invite Code"
hideOnClick={true}
>
<button className='' onClick={copyInviteCode}>
<Icon name="link-45deg" size="16" color="teal"/>
</button>
</Popup>
)}
{!user.isJoined && user.isExpiredInvite && (
<Popup
delay={500}
arrow
content="Generate Invite"
hideOnClick={true}
>
<button className='' onClick={generateInvite}>
<Icon name="link-45deg" size="16" color="red"/>
</button>
</Popup>
)}
</div>
<button className='' onClick={editHandler}>
<Icon name="pencil" color="teal" size="16" />
</button>

View file

@ -1,14 +1,11 @@
import React from 'react'
import { Styles } from '../../common';
import { AreaChart, ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
import { LineChart, Line, Legend } from 'recharts';
import cn from 'classnames';
import { AreaChart, ResponsiveContainer, XAxis, YAxis, Area, Tooltip } from 'recharts';
import CountBadge from '../../common/CountBadge';
import { numberWithCommas } from 'App/utils';
interface Props {
data: any;
// onClick?: (event, index) => void;
}
function CustomMetricOverviewChart(props: Props) {
const { data } = props;
@ -33,7 +30,7 @@ function CustomMetricOverviewChart(props: Props) {
<AreaChart
data={ data.chart }
margin={ {
top: 50, right: 0, left: 0, bottom: 1,
top: 50, right: 0, left: 0, bottom: 0,
} }
>
{gradientDef}
@ -60,7 +57,7 @@ function CustomMetricOverviewChart(props: Props) {
export default CustomMetricOverviewChart
const countView = (avg, unit) => {
const countView = (avg: any, unit: any) => {
if (unit === 'mb') {
if (!avg) return 0;
const count = Math.trunc(avg / 1024 / 1024);
@ -72,4 +69,4 @@ const countView = (avg, unit) => {
return numberWithCommas(count > 1000 ? count +'k' : count);
}
return avg ? numberWithCommas(avg): 0;
}
}

View file

@ -12,7 +12,7 @@ function CustomMetriPercentage(props: Props) {
return (
<div className="flex flex-col items-center justify-center" style={{ height: '240px'}}>
<div className="text-6xl">{numberWithCommas(data.count)}</div>
<div className="text-lg mt-6">{`${parseInt(data.previousCount || 0)} ( ${parseInt(data.countProgress || 0).toFixed(1)}% )`}</div>
<div className="text-lg mt-6">{`${parseInt(data.previousCount || 0)} ( ${Math.floor(parseInt(data.countProgress || 0))}% )`}</div>
<div className="color-gray-medium">from previous period.</div>
</div>
)

View file

@ -1,15 +1,80 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Pagination, NoContent } from 'UI';
import ErrorListItem from '../../../components/Errors/ErrorListItem';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { useModal } from 'App/components/Modal';
import ErrorDetailsModal from '../../../components/Errors/ErrorDetailsModal';
const PER_PAGE = 5;
interface Props {
metric: any;
isTemplate?: boolean;
isEdit?: boolean;
history: any,
location: any,
}
function CustomMetricTableErrors(props) {
function CustomMetricTableErrors(props: RouteComponentProps<Props>) {
const { metric, isEdit = false } = props;
const errorId = new URLSearchParams(props.location.search).get("errorId");
const { showModal, hideModal } = useModal();
const onErrorClick = (e: any, error: any) => {
e.stopPropagation();
props.history.replace({search: (new URLSearchParams({errorId : error.errorId})).toString()});
}
useEffect(() => {
if (!errorId) return;
showModal(<ErrorDetailsModal errorId={errorId} />, { right: true, onClose: () => {
if (props.history.location.pathname.includes("/dashboard")) {
props.history.replace({search: ""});
}
}});
return () => {
hideModal();
}
}, [errorId])
return (
<div>
</div>
<NoContent
show={!metric.data.errors || metric.data.errors.length === 0}
size="small"
>
<div className="pb-4">
{metric.data.errors && metric.data.errors.map((error: any, index: any) => (
<ErrorListItem key={index} error={error} onClick={(e) => onErrorClick(e, error)} />
))}
{isEdit && (
<div className="my-6 flex items-center justify-center">
<Pagination
page={metric.page}
totalPages={Math.ceil(metric.data.total / metric.limit)}
onPageChange={(page: any) => metric.updateKey('page', page)}
limit={metric.limit}
debounceRequest={500}
/>
</div>
)}
{!isEdit && (
<ViewMore total={metric.data.total} limit={metric.limit} />
)}
</div>
</NoContent>
);
}
export default CustomMetricTableErrors;
export default withRouter(CustomMetricTableErrors) as React.FunctionComponent<RouteComponentProps<Props>>;
const ViewMore = ({ total, limit }: any) => total > limit && (
<div className="mt-4 flex items-center justify-center cursor-pointer w-fit mx-auto">
<div className="text-center">
<div className="color-teal text-lg">
All <span className="font-medium">{total}</span> errors
</div>
</div>
</div>
);

View file

@ -1,48 +1,54 @@
import React from 'react';
import { useObserver } from 'mobx-react-lite';
import React, { useEffect } from 'react';
import SessionItem from 'Shared/SessionItem';
import { Pagination } from 'UI';
import { Pagination, NoContent } from 'UI';
import { useModal } from 'App/components/Modal';
const PER_PAGE = 10;
interface Props {
data: any
metric?: any
metric: any;
isTemplate?: boolean;
isEdit?: boolean;
}
function CustomMetricTableSessions(props: Props) {
const { data = { sessions: [], total: 0 }, isEdit = false, metric = {}, isTemplate } = props;
const currentPage = 1;
const { isEdit = false, metric } = props;
return (
<div>
{data.sessions && data.sessions.map((session: any, index: any) => (
<SessionItem session={session} />
))}
{isEdit && (
<div className="my-6 flex items-center justify-center">
<Pagination
page={currentPage}
totalPages={Math.ceil(data.total / PER_PAGE)}
onPageChange={(page: any) => this.props.updateCurrentPage(page)}
limit={PER_PAGE}
debounceRequest={500}
/>
</div>
)}
return useObserver(() => (
<NoContent
show={!metric || !metric.data || !metric.data.sessions || metric.data.sessions.length === 0}
size="small"
>
<div className="pb-4">
{metric.data.sessions && metric.data.sessions.map((session: any, index: any) => (
<div className="border-b last:border-none">
<SessionItem session={session} key={session.sessionId} />
</div>
))}
{isEdit && (
<div className="mt-6 flex items-center justify-center">
<Pagination
page={metric.page}
totalPages={Math.ceil(metric.data.total / metric.limit)}
onPageChange={(page: any) => metric.updateKey('page', page)}
limit={metric.data.total}
debounceRequest={500}
/>
</div>
)}
{!isEdit && (
<ViewMore total={data.total} />
)}
</div>
);
{!isEdit && (
<ViewMore total={metric.data.total} limit={metric.limit} />
)}
</div>
</NoContent>
));
}
export default CustomMetricTableSessions;
const ViewMore = ({ total }: any) => total > PER_PAGE && (
<div className="my-4 flex items-center justify-center cursor-pointer w-fit mx-auto">
const ViewMore = ({ total, limit }: any) => total > limit && (
<div className="mt-4 flex items-center justify-center cursor-pointer w-fit mx-auto">
<div className="text-center">
<div className="color-teal text-lg">
All <span className="font-medium">{total}</span> sessions

View file

@ -1,14 +0,0 @@
.wrapper {
background-color: $gray-light;
/* border: solid thin $gray-medium; */
border-radius: 3px;
padding: 20px;
}
.innerWapper {
border-radius: 3px;
width: 70%;
margin: 0 auto;
background-color: white;
min-height: 220px;
}

View file

@ -1,169 +0,0 @@
import React, { useEffect, useState, useRef } from 'react';
import { connect } from 'react-redux';
import { Loader, NoContent, SegmentSelection } from 'UI';
import { Styles } from '../../common';
import Period from 'Types/app/period';
import stl from './CustomMetricWidgetPreview.module.css';
import { remove } from 'Duck/customMetrics';
import DateRange from 'Shared/DateRange';
import { edit } from 'Duck/customMetrics';
import CustomMetriLineChart from '../CustomMetriLineChart';
import CustomMetricPercentage from '../CustomMetricPercentage';
import CustomMetricTable from '../CustomMetricTable';
import CustomMetricPieChart from '../CustomMetricPieChart';
const customParams = (rangeName: string) => {
const params = { density: 70 }
// if (rangeName === LAST_24_HOURS) params.density = 70
// if (rangeName === LAST_30_MINUTES) params.density = 70
// if (rangeName === YESTERDAY) params.density = 70
// if (rangeName === LAST_7_DAYS) params.density = 70
return params
}
interface Props {
metric: any;
data?: any;
onClickEdit?: (e) => void;
remove: (id) => void;
edit: (metric) => void;
}
function CustomMetricWidget(props: Props) {
const { metric } = props;
const [loading, setLoading] = useState(false)
const [data, setData] = useState<any>({ chart: [{}] })
const [period, setPeriod] = useState(Period({ rangeName: metric.rangeName, startDate: metric.startDate, endDate: metric.endDate }));
const colors = Styles.customMetricColors;
const params = customParams(period.rangeName)
const prevMetricRef = useRef<any>();
const isTimeSeries = metric.metricType === 'timeseries';
const isTable = metric.metricType === 'table';
useEffect(() => {
// Check for title change
if (prevMetricRef.current && prevMetricRef.current.name !== metric.name) {
prevMetricRef.current = metric;
return
};
prevMetricRef.current = metric;
setLoading(true);
}, [metric])
const onDateChange = (changedDates) => {
setPeriod({ ...changedDates, rangeName: changedDates.rangeValue })
props.edit({ ...changedDates, rangeName: changedDates.rangeValue });
}
const chagneViewType = (e, { name, value }) => {
props.edit({ [ name ]: value });
}
return (
<div className="mb-10">
<div className="flex items-center">
<div className="mr-auto font-medium">Preview</div>
<div className="flex items-center">
{isTimeSeries && (
<>
<span className="color-gray-medium mr-4">Visualization</span>
<SegmentSelection
name="viewType"
className="my-3"
primary
icons={true}
onSelect={ chagneViewType }
value={{ value: metric.viewType }}
list={ [
{ value: 'lineChart', name: 'Chart', icon: 'graph-up-arrow' },
{ value: 'progress', name: 'Progress', icon: 'hash' },
]}
/>
</>
)}
{isTable && (
<>
<span className="mr-1 color-gray-medium">Visualization</span>
<SegmentSelection
name="viewType"
className="my-3"
primary={true}
icons={true}
onSelect={ chagneViewType }
value={{ value: metric.viewType }}
list={[
{ value: 'table', name: 'Table', icon: 'table' },
{ value: 'pieChart', name: 'Chart', icon: 'pie-chart-fill' },
]}
/>
</>
)}
<div className="mx-4" />
<span className="mr-1 color-gray-medium">Time Range</span>
<DateRange
rangeValue={metric.rangeName}
startDate={metric.startDate}
endDate={metric.endDate}
onDateChange={onDateChange}
customRangeRight
direction="left"
/>
</div>
</div>
<div className={stl.wrapper}>
<div className={stl.innerWapper}>
<Loader loading={ loading } size="small">
<NoContent
size="small"
show={ data.length === 0 }
>
<div className="p-4 font-medium">
{metric.name}
</div>
<div className="px-4 pb-4">
{ isTimeSeries && (
<>
{ metric.viewType === 'progress' && (
<CustomMetricPercentage
data={data[0]}
colors={colors}
params={params}
/>
)}
{ metric.viewType === 'lineChart' && (
<CustomMetriLineChart
data={data}
// seriesMap={seriesMap}
colors={colors}
params={params}
/>
)}
</>
)}
{ isTable && (
<>
{ metric.viewType === 'table' ? (
<CustomMetricTable metric={metric} data={data[0]} />
) : (
<CustomMetricPieChart
metric={metric}
data={data[0]}
colors={colors}
/>
)}
</>
)}
</div>
</NoContent>
</Loader>
</div>
</div>
</div>
);
}
export default connect(null, { remove, edit })(CustomMetricWidget);

View file

@ -1 +0,0 @@
export { default } from './CustomMetricWidgetPreview';

View file

@ -7,7 +7,7 @@ import Chart from './Chart';
import ResourceInfo from './ResourceInfo';
import CopyPath from './CopyPath';
const cols = [
const cols: Array<Object> = [
{
key: 'resource',
title: 'Resource',
@ -17,7 +17,7 @@ const cols = [
{
key: 'sessions',
title: 'Sessions',
toText: count => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`,
toText: (count: number) => `${ count > 1000 ? Math.trunc(count / 1000) : count }${ count > 1000 ? 'k' : '' }`,
width: '20%',
},
{
@ -25,16 +25,17 @@ const cols = [
title: 'Trend',
Component: Chart,
width: '20%',
},
{
key: 'copy-path',
title: '',
Component: CopyPath,
cellClass: 'invisible group-hover:visible text-right',
width: '20%',
}
];
const copyPathCol = {
key: 'copy-path',
title: '',
Component: CopyPath,
cellClass: 'invisible group-hover:visible text-right',
width: '20%',
}
interface Props {
data: any
metric?: any
@ -43,6 +44,10 @@ interface Props {
function MissingResources(props: Props) {
const { data, metric, isTemplate } = props;
if (!isTemplate) {
cols.push(copyPathCol);
}
return (
<NoContent
title="No resources missing."

View file

@ -18,7 +18,7 @@ function ResourceLoadedVsResponseEnd(props: Props) {
size="small"
show={ metric.data.chart.length === 0 }
>
<ResponsiveContainer height={ 240 } width="100%">
<ResponsiveContainer height={ 246 } width="100%">
<ComposedChart
data={metric.data.chart}
margin={ Styles.chartMargins}
@ -67,4 +67,4 @@ function ResourceLoadedVsResponseEnd(props: Props) {
);
}
export default ResourceLoadedVsResponseEnd;
export default ResourceLoadedVsResponseEnd;

Some files were not shown because too many files have changed in this diff Show more