Skip to content

Commit

Permalink
feat: move login to react (#1031)
Browse files Browse the repository at this point in the history
* chore(frontend): Added initial test structure for auth tests

* feat(frontend): new tests on settings page

* fix(frontend): fixed e2e path for cypress tests

* fix(frontend): yarn.lock sry :(

* feat(frontend): added e2e on user login & auth

* chore(e2e): fixed tests path

* chore(e2e): Unquote server args

* fix(e2e): fix for multiple arguments for server launch

* fix(e2e): changed cypress invocation in accordance with new server params

* feat: make /login and /signup routes to be served with PWA

* ref(frontend) add login page basic layout

* ref(frontend) add signup page, intro pages styling

* ref(frontend) add not found and forbidden pages

* chore(webapp): move intro pages colors to global variables

* feat(frontend): add log in page functionality

* feat(frontend): add sign up page functionality

* feat(webapp): add log in page external auth links

* fix(webapp): fix redirect condition typo

* chore(webapp): move intro pages; refactor login/signup styles and service methods

* chore(webapp): change InputField style prop naming

* chore(webapp): fix event and prop types

* chore(webapp): add error typing, refactor inputfield styles application

* chore(webapp): refactor inputField styles application

* chore: ignore indexHandler cognitive complexity

* Feat: move login to react (#985)

* feat: make /login and /signup routes to be served with PWA

* ref(frontend) add login page basic layout

* ref(frontend) add signup page, intro pages styling

* ref(frontend) add not found and forbidden pages

* chore(webapp): move intro pages colors to global variables

* feat(frontend): add log in page functionality

* feat(frontend): add sign up page functionality

* feat(webapp): add log in page external auth links

* fix(webapp): fix redirect condition typo

* chore(webapp): move intro pages; refactor login/signup styles and service methods

* chore(webapp): change InputField style prop naming

* chore(webapp): fix event and prop types

* chore(webapp): add error typing, refactor inputfield styles application

* chore(webapp): refactor inputField styles application

* chore: ignore indexHandler cognitive complexity

Co-authored-by: Antony Shaleynikov <[email protected]>

* fix(webapp): change imports

* feat: updated e2e tests in accordance with API changes

* fix: fixed popup error on login screen

* chore: move auth vars to index page (#991)

* chore: move auth vars to index page

* chore: auth vars objects

* feat: added 404 handler to render template page

* fix: eliminate possible merge issues

* fix: resolve linting error

* fix: more old merge issues

* fix: fixing auth e2e and merge issues

* fix: removed complexity error on go lint

* feat: Added 404 handling on react side

* fix: commiting index handlers on 404

* fix: fixing e2e tests

* fix: added baseURL support

* fix: fixed broken sidebar

* fix: added features to index renderer

* fix: forgotten lockfile

* fix: update broken run-p on mac

* fix: Maybe(fix) of e2e race condition

* fix: maybe(fix) fixing exception while data isn't yet populated to flamegraph

* fix: maybe(fix) of e2e test

* fix: another possible fix for race condition

* fix: revert previous fix

* chore: hide sidebar on intro pages

* chore: adjust oAuth buttons logic/styles on login

* feat: added is signup enabled feature in server

* chore: handle if signup enabled on login page

* chore: adjust horizontal scroll on intro pages

* fix: sidebar visibility on settings routes

* fix: no user on Settings

* chore: adjust signup/login pages user redirect

* covers more cases, e.g when internal auth enabled but sign up disabled, etc

* lint fix?

* lint fix?

* base url fixes

* improvements

* fixes

* updates styling a bit

* fix

* fix(maybe): highlight cy test

Co-authored-by: Antony Shaleynikov <[email protected]>
Co-authored-by: Egor Mozheiko <[email protected]>
Co-authored-by: EgorMozheiko <[email protected]>
Co-authored-by: Anton Kolesnikov <[email protected]>
Co-authored-by: pavelpashkovsky <[email protected]>
  • Loading branch information
6 people authored Apr 15, 2022
1 parent b20bd5d commit 1cb6f9a
Show file tree
Hide file tree
Showing 50 changed files with 1,201 additions and 512 deletions.
64 changes: 64 additions & 0 deletions .github/workflows/cypress-tests-auth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Cypress Auth Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
cypress-tests:
runs-on: ubuntu-latest
container: cypress/included:8.6.0
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.17.0'
- name: Cache go mod directories
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"

- uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn
- name: Cypress run
uses: cypress-io/github-action@v2
with:
build: make e2e-build
wait-on: http://localhost:4040
start: make server -- --auth.internal.enabled --auth.internal.signup-enabled
config-file: cypress/integration/auth/cypress.json
env:
# keep the server quiet
PYROSCOPE_LOG_LEVEL: error
ENABLED_SPIES: none
CYPRESS_VIDEO: true
CYPRESS_COMPARE_SNAPSHOTS: true
- uses: actions/upload-artifact@v2
if: always()
with:
name: cypress-screenshots
path: cypress/screenshots
- uses: actions/upload-artifact@v2
if: always()
with:
name: cypress-videos
path: cypress/videos
- uses: actions/upload-artifact@v2
if: always()
with:
name: cypress-snapshots
path: cypress/snapshots
2 changes: 1 addition & 1 deletion .github/workflows/cypress-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
build: make e2e-build
wait-on: http://localhost:4040
start: make server
config-file: webapp/cypress.json
config-file: cypress/cypress.json
env:
# keep the server quiet
PYROSCOPE_LOG_LEVEL: error
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ GOBUILD=go build -trimpath
ARCH ?= $(shell uname -m)
OS ?= $(shell uname)

SERVERTARGETS ?= dev server
SERVERPARAMS ?=$(filter-out $(SERVERTARGETS), $(MAKECMDGOALS))

# if you change the name of this variable please change it in generate-git-info.sh file
PHPSPY_VERSION ?= be3abd72e8e2dd5dd4e61008fcd702f90c6eb238

Expand Down Expand Up @@ -148,7 +151,7 @@ coverage: ## Runs the test suite with coverage

.PHONY: server
server: ## Start the Pyroscope Server
bin/pyroscope server
bin/pyroscope server $(SERVERPARAMS)

.PHONY: install-web-dependencies
install-web-dependencies: ## Install the web dependencies
Expand Down
1 change: 1 addition & 0 deletions webapp/cypress.json → cypress/cypress.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"video": false,
"baseUrl": "http://localhost:4040",
"integrationFolder": "cypress/integration/webapp",
"testFiles": "*.ts",
"retries": {
"runMode": 5
}
Expand Down
30 changes: 30 additions & 0 deletions cypress/integration/auth/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// few tests just to quickly validate the endpoints are working
describe('unauth API tests', () => {
it('it should respond with 401 on unauthorized access', () => {
cy.request({
method: 'GET',
url: '/render?from=now-5m&until=now&query=pyroscope.server.alloc_objects%7B%7D&format=json',
failOnStatusCode: false,
})
.its('status')
.should('eq', 401);
});

it('it should respond with 200 on login and signup', () => {
cy.request({
method: 'GET',
url: '/login',
failOnStatusCode: false,
})
.its('status')
.should('eq', 200);

cy.request({
method: 'GET',
url: '/signup',
failOnStatusCode: false,
})
.its('status')
.should('eq', 200);
});
});
9 changes: 9 additions & 0 deletions cypress/integration/auth/cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"video": false,
"baseUrl": "http://localhost:4040",
"integrationFolder": "cypress/integration/auth",
"testFiles": "*.ts",
"retries": {
"runMode": 5
}
}
64 changes: 64 additions & 0 deletions cypress/integration/auth/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// few tests just to quickly validate the endpoints are working
describe('Settings page', () => {
it('should display error when log in with random creds', () => {
cy.visit('/login');

cy.get('input#username').focus().type('random');
cy.get('input#password').focus().type('user');
cy.get('button.sign-in-button').click();
cy.get('#error').should('contain.text', 'invalid credentials');
// Expect it not to be redirected to main page
cy.url().should('contain', '/login');
});

it('should be able to log in with default creds', () => {
cy.visit('/login');

cy.get('input#username').focus().type('admin');
cy.get('input#password').focus().type('admin');
cy.get('button.sign-in-button').click();

// Expect it to be redirected to main page
cy.url().should('contain', '/?query=');

cy.visit('/logout');
});

it.only('should be able to see correct settings page', () => {
cy.visit('/login');

cy.get('input#username').focus().type('admin');
cy.get('input#password').focus().type('admin');
cy.findByTestId('sign-in-button').click();

cy.findByTestId('sidebar-settings').click();
cy.url().should('contain', '/settings');

cy.findByTestId('settings-userstab').click();

cy.url().should('contain', '/settings/users');

cy.findByTestId('settings-adduser').click();
cy.url().should('contain', '/settings/users/add');

cy.get('#userAddName').type('user');
cy.get('#userAddPassword').type('user');
cy.get('#userAddEmail').type('[email protected]');
cy.get('#userAddFullName').type('Readonly User');
cy.findByTestId('settings-useradd').click();

cy.url().should('contain', '/settings/users');

cy.visit('/logout');
cy.visit('/login');

cy.get('input#username').focus().type('user');
cy.get('input#password').focus().type('user');
cy.findByTestId('sign-in-button').click();

// Expect it to be redirected to main page
cy.url().should('contain', '/?query=');

cy.visit('/logout');
});
});
4 changes: 4 additions & 0 deletions cypress/integration/webapp/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ describe('basic test', () => {
cy.wait('@render');
cy.wait('@render');

cy.waitForFlamegraphToRender();

// This test has a race condition, since it does not wait for the canvas to be rendered
cy.findByTestId('flamegraph-tooltip').should('not.be.visible');
cy.waitForFlamegraphToRender().trigger('mousemove', 0, 0);
Expand Down Expand Up @@ -319,6 +321,8 @@ describe('basic test', () => {

cy.findByTestId('flamegraph-highlight').should('not.be.visible');

cy.wait(500);

cy.waitForFlamegraphToRender().trigger('mousemove', 0, 0);
cy.findByTestId('flamegraph-highlight').should('be.visible');
});
Expand Down
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@
"cy:ci": "yarn cy:webapp:ci",
"cy:ss": "yarn cy:webapp:ss",
"cy:ss-check": "yarn cy:webapp:ss-check",
"cy:webapp:open": "cypress open --config-file webapp/cypress.json",
"cy:webapp:ci": "cypress run --config-file webapp/cypress.json",
"cy:webapp:ss": "./scripts/cypress-screenshots.sh --config-file webapp/cypress.json",
"cy:webapp:ss-check": "CYPRESS_updateSnapshots=false ./scripts/cypress-screenshots.sh --config-file webapp/cypress.json",
"cy:webapp:open": "cypress open --config-file cypress/cypress.json",
"cy:webapp:ci": "cypress run --config-file cypress/cypress.json",
"cy:webapp:ss": "./scripts/cypress-screenshots.sh --config-file cypress/cypress.json",
"cy:webapp:ss-check": "CYPRESS_updateSnapshots=false ./scripts/cypress-screenshots.sh --config-file cypress/cypress.json",
"cy:webapp-auth:open": "cypress open --config-file cypress/integration/auth/cypress.json",
"cy:webapp-auth:ci": "cypress run --config-file cypress/integration/auth/cypress.json",
"cy:webapp-auth:ss-check": "CYPRESS_updateSnapshots=false ./scripts/cypress-screenshots.sh --config-file cypress/integration/auth/cypress.json",
"cy:webapp-base-url:open": "cypress open --config-file cypress/base-url/cypress.json",
"cy:webapp-base-url:ci": "cypress run --config-file cypress/base-url/cypress.json",
"cy:webapp-base-url:ss-check": "CYPRESS_updateSnapshots=false ./scripts/cypress-screenshots.sh --config-file cypress/base-url/cypress.json",
Expand Down
2 changes: 1 addition & 1 deletion packages/pyroscope-flamegraph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"type-check": "tsc -p tsconfig.json --noEmit",
"lint": "eslint ./ --cache --fix",
"dev": "NODE_ENV=production webpack --config ../../scripts/webpack/webpack.flamegraph.ts --watch",
"watch": "run-p build:types:watch dev"
"watch": "yarn build:types:watch & yarn dev"
},
"peerDependencies": {
"react": ">=16.14.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,11 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
]);

const renderCanvas = () => {
canvasRef?.current?.setAttribute('data-state', 'rendering');
flamegraph?.current?.render();
canvasRef?.current?.setAttribute('data-state', 'rendered');
if (canvasRef && canvasRef.current && flamegraph && flamegraph.current) {
canvasRef?.current?.setAttribute('data-state', 'rendering');
flamegraph?.current?.render();
canvasRef?.current?.setAttribute('data-state', 'rendered');
}
};

const dataUnavailable =
Expand Down
70 changes: 70 additions & 0 deletions pkg/baseurl/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package baseurl

import (
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"

"github.com/pyroscope-io/pyroscope/pkg/config"
"github.com/sirupsen/logrus"
)

var upstream string
var baseURL string
var listenAddr string

// newProxy takes target host and creates a reverse proxy
func newProxy(targetHost string) (http.Handler, error) {
target, err := url.Parse(targetHost)
if err != nil {
return nil, err
}

p := httputil.NewSingleHostReverseProxy(target)
p.Director = func(r *http.Request) {
logrus.Info("before: ", r.URL.String())
r.URL.Scheme = target.Scheme
r.URL.Host = target.Host
logrus.Info("target.Host", target.Host)
r.URL.Path = strings.ReplaceAll(r.URL.Path, target.Path, "")
r.URL.RawPath = strings.ReplaceAll(r.URL.RawPath, target.Path, "")
logrus.Info("after: ", r.URL.String())
}

f := func(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, target.Path) {
w.WriteHeader(404)
w.Write([]byte(fmt.Sprintf("Please visit pyroscope at %s", target.Path)))
return
}
p.ServeHTTP(w, r)
}
return http.HandlerFunc(f), nil
}

func Start(cfg *config.Server) {
proxy, err := newProxy("http://localhost" + cfg.APIBindAddr + cfg.BaseURL)

if err != nil {
panic(err)
}

logger := logrus.New()
w := logger.Writer()
server := &http.Server{
Addr: cfg.BaseURLBindAddr,
Handler: proxy,
ReadTimeout: 10 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 30 * time.Second,
MaxHeaderBytes: 1 << 20,

ErrorLog: log.New(w, "", 0),
}

server.ListenAndServe()
}
4 changes: 4 additions & 0 deletions pkg/cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"gopkg.in/yaml.v2"

// revive:disable:blank-imports register discoverer
"github.com/pyroscope-io/pyroscope/pkg/baseurl"
_ "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/file"
_ "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/http"
_ "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/kubernetes"
Expand Down Expand Up @@ -204,6 +205,9 @@ func (svc *serverService) Start() error {
})
}

if svc.config.BaseURLBindAddr != "" {
go baseurl.Start(svc.config)
}
go svc.debugReporter.Start()
if svc.analyticsService != nil {
go svc.analyticsService.Start()
Expand Down
7 changes: 4 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,10 @@ type Server struct {
LogLevel string `def:"info" desc:"log level: debug|info|warn|error" mapstructure:"log-level"`
BadgerLogLevel string `def:"error" desc:"log level: debug|info|warn|error" mapstructure:"badger-log-level"`

StoragePath string `def:"<installPrefix>/var/lib/pyroscope" desc:"directory where pyroscope stores profiling data" mapstructure:"storage-path"`
APIBindAddr string `def:":4040" desc:"port for the HTTP(S) server used for data ingestion and web UI" mapstructure:"api-bind-addr"`
BaseURL string `def:"" desc:"base URL for when the server is behind a reverse proxy with a different path" mapstructure:"base-url"`
StoragePath string `def:"<installPrefix>/var/lib/pyroscope" desc:"directory where pyroscope stores profiling data" mapstructure:"storage-path"`
APIBindAddr string `def:":4040" desc:"port for the HTTP(S) server used for data ingestion and web UI" mapstructure:"api-bind-addr"`
BaseURL string `def:"" desc:"base URL for when the server is behind a reverse proxy with a different path" mapstructure:"base-url"`
BaseURLBindAddr string `def:"" deprecated:"true" desc:"server for debugging base url" mapstructure:"base-url-bind-addr"`

CacheEvictThreshold float64 `def:"0.25" desc:"percentage of memory at which cache evictions start" mapstructure:"cache-evict-threshold"`
CacheEvictVolume float64 `def:"0.33" desc:"percentage of cache that is evicted per eviction run" mapstructure:"cache-evict-volume"`
Expand Down
2 changes: 2 additions & 0 deletions pkg/server/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ func (ctrl *Controller) serverMux() (http.Handler, error) {
{"/healthz", ctrl.healthz},
})

r.NotFoundHandler = ctrl.notfoundHandler()

return r, nil
}

Expand Down
Loading

0 comments on commit 1cb6f9a

Please sign in to comment.