diff --git a/.circleci/config.yml b/.circleci/config.yml index 09eb5de505..db8c46cd7a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,35 @@ version: 2.1 +commands: + init-app-dependencies: + steps: + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: | + yarn install --frozen-lockfile + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + install_rust_compiler: + description: > + This will install the rust compiler with the rust tools. + steps: + - run: + name: Install clang + command: sudo apt-get update && sudo apt-get install -y --no-install-recommends clang musl-tools + - run: + name: Install rust compiler + command: | + curl https://sh.rustup.rs -sSf | \ + sh -s -- --default-toolchain stable -y + echo 'source $HOME/.cargo/env' >> $BASH_ENV + jobs: cloud-e2e: machine: @@ -105,6 +135,33 @@ jobs: name: "Save Yarn Package Cache" paths: - ~/.cache/yarn + oss-e2e: + docker: + - image: circleci/golang:1.15-node-browsers + steps: + - checkout + - init-app-dependencies + - run: sudo apt-get update + - run: sudo apt-get install -y netcat-openbsd + - run: sudo apt-get install -y bzr + - install_rust_compiler + - run: + name: Build UI + command: | + yarn build + - run: + name: Clone and 'make' influxdb from master + command: | + yarn script db:get + - run: + name: Start influxdb + command: | + yarn script db:run + background: true + - run: + name: Run e2e tests + command: | + yarn test:e2e workflows: version: 2 @@ -115,3 +172,4 @@ workflows: e2e: jobs: - cloud-e2e + - oss-e2e diff --git a/.gitignore b/.gitignore index dc79cd9877..d8b49cfaca 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ build coverage node_modules vendor +temp +cypress/screenshots +cypress/videos +junit-results # files cypress.env.json diff --git a/README.md b/README.md index 6ba288a11d..7a2c3473c9 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,12 @@ From the ui directory. Build the javascript with To run Cypress locally `$ yarn cy:dev` +### Run against influxdb master + 1. `yarn build` + 1. `yarn script db:get` clone database with git and build it + 1. `yarn script db:run` starts database (don't close terminal) + 1. `yarn cy` with ui or `yarn test:e2e` only run tests + ## Starting Dev Server Running `/ui` locally depends on `monitor-ci`. [See the monitor-ci Quickstart](https://github.com/influxdata/monitor-ci#quickstart-for-local-development) diff --git a/cypress/e2e/buckets.test.ts b/cypress/e2e/buckets.test.ts index 193832c97a..3665919680 100644 --- a/cypress/e2e/buckets.test.ts +++ b/cypress/e2e/buckets.test.ts @@ -49,6 +49,31 @@ describe('Buckets', () => { cy.getByTestID('inline-labels--empty').should('exist') }) + it('can create a bucket with retention', () => { + const newBucket = '🅱️ucket' + cy.getByTestID(`bucket-card ${newBucket}`).should('not.exist') + + //create bucket with retention + cy.getByTestID('Create Bucket').click() + cy.getByTestID('overlay--container').within(() => { + cy.getByInputName('name').type(newBucket) + cy.getByTestID('retention-intervals--button').click() + cy.getByTestID('duration-selector--button').click() + cy.getByTestID('duration-selector--12h') + .click() + .then(() => { + cy.getByTestID('bucket-form-submit').click() + }) + }) + + //assert bucket with retention + cy.getByTestID(`bucket-card ${newBucket}`) + .should('exist') + .within(() => { + cy.getByTestID('bucket-retention').should('contain', '12 hours') + }) + }) + it("can update a bucket's retention rules", () => { cy.get('@bucket').then(({name}: Bucket) => { cy.getByTestID(`bucket-settings`).click() @@ -131,30 +156,42 @@ describe('Buckets', () => { expect(testID).to.include(retentionAsc[index]) }) }) - - cy.getByTestID('search-widget').type('tasks') - cy.get('.cf-resource-card').should('have.length', 1) }) }) - // Currently producing a false negative - it.skip('can delete a bucket', () => { - const bucket1 = 'newbucket1' - cy.get('@org').then(({id, name}: Organization) => { - cy.createBucket(id, name, bucket1) - }) + it('can filter buckets', () => { + //assert buckets amount + cy.get('.cf-resource-card').should('have.length', 3) - cy.getByTestID(`context-delete-menu ${bucket1}`).click() - cy.getByTestID(`context-delete-bucket ${bucket1}`).click() + //filter a bucket + cy.getByTestID('search-widget').type('def') + cy.get('.cf-resource-card') + .should('have.length', 1) + .should('contain', 'defbuck') - // normally we would assert for empty state here - // but we cannot because of the default system buckets - // since cypress selectors are so fast, that sometimes a bucket - // that is deleted will be selected before it gets deleted - cy.wait(10000) + //clear filter and assert all buckets are visible + cy.getByTestID('search-widget').clear() + cy.get('.cf-resource-card').should('have.length', 3) + }) + }) - cy.getByTestID(`bucket--card--name ${bucket1}`).should('not.exist') + // Currently producing a false negative + it.skip('can delete a bucket', () => { + const bucket1 = 'newbucket1' + cy.get('@org').then(({id, name}: Organization) => { + cy.createBucket(id, name, bucket1) }) + + cy.getByTestID(`context-delete-menu ${bucket1}`).click() + cy.getByTestID(`context-delete-bucket ${bucket1}`).click() + + // normally we would assert for empty state here + // but we cannot because of the default system buckets + // since cypress selectors are so fast, that sometimes a bucket + // that is deleted will be selected before it gets deleted + cy.wait(10000) + + cy.getByTestID(`bucket--card--name ${bucket1}`).should('not.exist') }) // skipping until feature flag feature is removed for deleteWithPredicate @@ -394,5 +431,97 @@ describe('Buckets', () => { // mymeasurement comes from fixtures/data.txt cy.getByTestID('selector-list mymeasurement').should('exist') }) + + it('create scraper', () => { + //click "add data" and choose Scrape Metrics + cy.getByTestID('add-data--button').click() + cy.get('.bucket-add-data--option') + .contains('Scrape Metrics') + .click() + + //fill out name and assert default bucket + cy.getByTitle('Name') + .clear() + .type('Scraper from bucket') + cy.getByTestID('bucket-dropdown--button').should('contain', 'defbuck') + cy.getByTestID('create-scraper--submit').click() + + //assert notification + cy.getByTestID('notification-success').should( + 'contain', + 'Scraper was created successfully' + ) + + //assert created scraper's parameters + cy.getByTestID('tabs--tab') + .contains('Scrapers') + .click() + + cy.getByTestID('resource-card') + .first() + .within(() => { + cy.getByTestID('resource-editable-name').should( + 'contain', + 'Scraper from bucket' + ) + cy.getByTestID('resource-list--meta').should('contain', 'defbuck') + }) + }) + + it('configure telegraf agent', () => { + //click "add data" and choose Configure Telegraf Agent + cy.getByTestID('add-data--button').click() + cy.get('.bucket-add-data--option') + .contains('Configure Telegraf Agent') + .click() + + //assert default bucket + cy.getByTestID('bucket-dropdown--button').should('contain', 'defbuck') + + //filter plugins and choose system + cy.getByTestID('input-field') + .type('sys') + .then(() => { + cy.getByTestID('square-grid--card').should('have.length', 1) + cy.getByTestID('input-field') + .clear() + .then(() => { + cy.getByTestID('square-grid--card').should('have.length', 5) + }) + }) + cy.getByTestID('telegraf-plugins--System').click() + cy.getByTestID('next').click() + + //add telegraf name and description + cy.getByTitle('Telegraf Configuration Name') + .clear() + .type('Telegraf from bucket') + cy.getByTitle('Telegraf Configuration Description') + .clear() + .type('This is a telegraf description') + cy.getByTestID('next').click() + + //assert notifications + cy.getByTestID('notification-success').should( + 'contain', + 'Your configurations have been saved' + ) + cy.getByTestID('notification-success').should( + 'contain', + 'Successfully created dashboards for telegraf plugin: system.' + ) + cy.getByTestID('next').click() + + //assert telegraf card parameters + cy.getByTestID('collector-card--name').should( + 'contain', + 'Telegraf from bucket' + ) + cy.getByTestID('resource-list--editable-description').should( + 'contain', + 'This is a telegraf description' + ) + cy.getByTestID('bucket-name').should('contain', 'defbuck') + }) }) }) diff --git a/cypress/e2e/dashboardsIndex.test.ts b/cypress/e2e/dashboardsIndex.test.ts index ef189002dd..6c87ee632c 100644 --- a/cypress/e2e/dashboardsIndex.test.ts +++ b/cypress/e2e/dashboardsIndex.test.ts @@ -2,6 +2,7 @@ import {Organization} from '../../src/types' const newLabelName = 'click-me' const dashboardName = 'Bee Happy' +const dashboardName2 = 'test dashboard' const dashSearchName = 'bEE' describe('Dashboards', () => { @@ -36,7 +37,9 @@ describe('Dashboards', () => { }) }) - it('can CRUD dashboards from empty state, and a header', () => { + it('can CRUD dashboards from empty state, header, and a Template', () => { + const newName = 'new 🅱️ashboard' + // Create from empty state cy.getByTestID('empty-dashboards-list').within(() => { cy.getByTestID('add-resource-dropdown--button').click() @@ -52,8 +55,6 @@ describe('Dashboards', () => { }) }) - const newName = 'new 🅱️ashboard' - cy.getByTestID('dashboard-card').within(() => { cy.getByTestID('dashboard-card--name') .first() @@ -85,7 +86,20 @@ describe('Dashboards', () => { }) }) - cy.getByTestID('dashboard-card').should('have.length', 2) + // Create from Template + cy.get('@org').then(({id}: Organization) => { + cy.createDashboardTemplate(id) + }) + + cy.getByTestID('empty-dashboards-list').within(() => { + cy.getByTestID('add-resource-dropdown--button').click() + cy.getByTestID('add-resource-dropdown--template').click() + }) + cy.getByTestID('template--Bashboard-Template').click() + cy.getByTestID('template-panel').should('exist') + cy.getByTestID('create-dashboard-button').click() + + cy.getByTestID('dashboard-card').should('have.length', 3) // Delete dashboards cy.getByTestID('dashboard-card') @@ -96,6 +110,42 @@ describe('Dashboards', () => { cy.getByTestID('context-delete-dashboard').click() }) + cy.getByTestID('dashboard-card') + .first() + .trigger('mouseover') + .within(() => { + cy.getByTestID('context-delete-menu').click() + cy.getByTestID('context-delete-dashboard').click() + }) + + const dashboardDescription = 'this dashboard contains secret information' + + // change description + cy.getByTestID('resource-list--editable-description') + .click('topLeft') + .within(() => { + cy.getByTestID('input-field') + .type(dashboardDescription) + .type('{enter}') + }) + cy.getByTestID('resource-list--editable-description').should( + 'contain', + dashboardDescription + ) + + // remove description + cy.getByTestID('resource-list--editable-description') + .click('topLeft') + .within(() => { + cy.getByTestID('input-field') + .clear() + .type('{enter}') + }) + cy.getByTestID('resource-list--editable-description').should( + 'not.contain', + dashboardDescription + ) + cy.getByTestID('dashboard-card') .first() .trigger('mouseover') @@ -107,6 +157,73 @@ describe('Dashboards', () => { cy.getByTestID('empty-dashboards-list').should('exist') }) + it('can import as JSON or file', () => { + const checkImportedDashboard = () => { + // wait for importing done + cy.wait(100) + cy.getByTestID('dashboard-card--name') + .should('contain', 'IMPORT dashboard') + .click() + cy.getByTestID('markdown-cell--contents').should( + 'contain', + 'Note about no tea' + ) + cy.getByTestID('cell cellll').should('exist') + + // return to previous page + cy.fixture('routes').then(({orgs}) => { + cy.get('@org').then(({id}: Organization) => { + cy.visit(`${orgs}/${id}/dashboards-list`) + }) + }) + } + + // import dashboard from file + cy.getByTestID('add-resource-dropdown--button') + .first() + .click() + cy.getByTestID('add-resource-dropdown--import').click() + + cy.getByTestID('drag-and-drop--input').attachFile({ + filePath: 'dashboard-import.json', + }) + + cy.getByTestID('submit-button Dashboard').click() + checkImportedDashboard() + + // delete dashboard before reimport + cy.getByTestID('dashboard-card') + .first() + .trigger('mouseover') + .within(() => { + cy.getByTestID('context-delete-menu').click() + cy.getByTestID('context-delete-dashboard').click() + }) + + // dasboard no longer exists + cy.getByTestID('dashboard-card').should('not.exist') + + // import dashboard as json + cy.getByTestID('add-resource-dropdown--button') + .first() + .click() + cy.getByTestID('add-resource-dropdown--import').click() + + cy.getByTestID('select-group--option') + .contains('Paste') + .click() + + cy.fixture('dashboard-import.json').then(json => { + cy.getByTestID('import-overlay--textarea') + .should('be.visible') + .click() + .type(JSON.stringify(json), {parseSpecialCharSequences: false}) + }) + + cy.getByTestID('submit-button Dashboard').click() + checkImportedDashboard() + }) + it('keeps user input in text area when attempting to import invalid JSON', () => { cy.getByTestID('page-control-bar').within(() => { cy.getByTestID('add-resource-dropdown--button').click() @@ -139,7 +256,7 @@ describe('Dashboards', () => { cy.createAndAddLabel('dashboards', id, body.id, newLabelName) }) - cy.createDashboard(id).then(({body}) => { + cy.createDashboard(id, dashboardName2).then(({body}) => { cy.createAndAddLabel('dashboards', id, body.id, 'bar') }) }) @@ -158,6 +275,12 @@ describe('Dashboards', () => { .first() .click({force: true}) + cy.fixture('routes').then(({orgs}) => { + cy.get('@org').then(({id}: Organization) => { + cy.visit(`${orgs}/${id}/dashboards-list`) + }) + }) + cy.getByTestID('dashboard-card').should('have.length', 3) }) @@ -377,21 +500,37 @@ describe('Dashboards', () => { cy.contains(dashboardName) }) }) + + it('can list and search dashboards on home page', () => { + cy.getByTestID('tree-nav--header').click() + + cy.getByTestID('recent-dashboards--panel').within(() => { + const dashboardIsVisible = (name: string, isVisible = true) => { + cy.contains(name).should((isVisible ? '' : 'not.') + 'visible') + } + + dashboardIsVisible(dashboardName) + dashboardIsVisible(dashboardName2) + + cy.get('input').type(dashSearchName) + + dashboardIsVisible(dashboardName) + dashboardIsVisible(dashboardName2, false) + }) + }) }) - describe('When a dashboard does not exist', () => { - it('should route the user to the dashboard index page', () => { - const nonexistentID = '/0499992503cd3700' + it('should redirect to the dashboard list when dashboard not exist', () => { + const nonexistentID = '0499992503cd3700' - // visiting the dashboard edit page - cy.get('@org').then(({id}: Organization) => { - cy.fixture('routes').then(({orgs, dashboards}) => { - cy.visit(`${orgs}/${id}${dashboards}${nonexistentID}`) - cy.url().should( - 'eq', - `${Cypress.config().baseUrl}${orgs}/${id}/dashboards-list` - ) - }) + // visiting the dashboard edit page + cy.get('@org').then(({id}: Organization) => { + cy.fixture('routes').then(({orgs, dashboards}) => { + cy.visit(`${orgs}/${id}${dashboards}/${nonexistentID}`) + cy.url().should( + 'eq', + `${Cypress.config().baseUrl}${orgs}/${id}/dashboards-list` + ) }) }) }) diff --git a/cypress/e2e/dashboardsView.test.ts b/cypress/e2e/dashboardsView.test.ts index 076e60d973..fe1c4ed988 100644 --- a/cypress/e2e/dashboardsView.test.ts +++ b/cypress/e2e/dashboardsView.test.ts @@ -107,6 +107,9 @@ describe('Dashboard', () => { cy.getByTestID('save-note--button').click() }) + cy.getByTestID('cell Name this Cell').should('contain', noteText) + cy.getByTestID('cell Name this Cell').should('not.contain', headerPrefix) + //Note Cell controls cy.getByTestID('add-note--button').click() cy.getByTestID('note-editor--overlay').should('be.visible') @@ -148,6 +151,35 @@ describe('Dashboard', () => { key: 'Escape', }) + // Edit note cell + cy.getByTestID('cell-context--toggle') + .last() + .click() + cy.getByTestID('cell-context--note').click() + + // Note cell + const noteText2 = 'changed text' + const headerPrefix2 = '-' + + cy.getByTestID('note-editor--overlay').within(() => { + cy.get('.CodeMirror') + .then($codeMirror => { + const len = $codeMirror[0].innerText.length + cy.wrap($codeMirror).type('{backspace}'.repeat(len)) + }) + .type(`${headerPrefix2} ${noteText2}`) + cy.getByTestID('note-editor--preview').contains(noteText2) + cy.getByTestID('note-editor--preview').should( + 'not.contain', + headerPrefix2 + ) + + cy.getByTestID('save-note--button').click() + }) + + cy.getByTestID('cell Name this Cell').should('not.contain', noteText) + cy.getByTestID('cell Name this Cell').should('contain', noteText2) + // Remove Note cell cy.getByTestID('cell-context--toggle') .last() @@ -253,13 +285,17 @@ describe('Dashboard', () => { it('can manage variable state with a lot of pointing and clicking', () => { const bucketOne = 'b1' const bucketThree = 'b3' + let defaultBucket = '' cy.get('@org').then(({id: orgID}: Organization) => { cy.get('@dashboard').then(({dashboard}) => { - cy.createCSVVariable(orgID, 'bucketsCSV', [ - bucketOne, - Cypress.env('bucket'), - bucketThree, - ]) + cy.fixture('user').then(({bucket}) => { + defaultBucket = bucket + cy.createCSVVariable(orgID, 'bucketsCSV', [ + bucketOne, + defaultBucket, + bucketThree, + ]) + }) cy.createQueryVariable(orgID) cy.createMapVariable(orgID).then(() => { @@ -338,14 +374,12 @@ describe('Dashboard', () => { cy.getByTestID('variable-dropdown--button') .eq(0) .click() - cy.get(`#${Cypress.env('bucket')}`).click() + cy.get(`#${defaultBucket}`).click() // and that it updates the variable in the URL without breaking stuff cy.location('search').should( 'eq', - `?lower=now%28%29%20-%201h&vars%5BbucketsCSV%5D=${Cypress.env( - 'bucket' - )}` + `?lower=now%28%29%20-%201h&vars%5BbucketsCSV%5D=${defaultBucket}` ) // open VEO @@ -355,7 +389,7 @@ describe('Dashboard', () => { // selected value in cell context is 2nd value cy.window() .pipe(getSelectedVariable(dashboard.id, 0)) - .should('equal', Cypress.env('bucket')) + .should('equal', defaultBucket) cy.getByTestID('toolbar-tab').click() cy.get('.flux-toolbar--list-item') @@ -476,7 +510,7 @@ describe('Dashboard', () => { cy.getByTestID('variable-dropdown--button') .eq(0) .click() - cy.get(`#${Cypress.env('bucket')}`).click() + cy.get(`#${defaultBucket}`).click() // assert visualization appears cy.getByTestID('giraffe-layer-line').should('exist') @@ -731,4 +765,96 @@ describe('Dashboard', () => { }) }) }) + + //based on issue #18339 + it('should save a time format change and show in the dashboard cell card', () => { + const numLines = 360 + cy.writeData(lines(numLines)) + cy.get('@org').then(({id: orgID}: Organization) => { + cy.createDashboard(orgID).then(({body}) => { + cy.fixture('routes').then(({orgs}) => { + cy.visit(`${orgs}/${orgID}/dashboards/${body.id}`) + }) + }) + }) + const timeFormatOriginal = 'YYYY-MM-DD HH:mm:ss ZZ' + const timeFormatNew = 'hh:mm a' + + //creating new dashboard cell + cy.getByTestID('add-cell--button').click() + cy.getByTestID(`selector-list m`).click() + cy.getByTestID('selector-list v').click() + cy.getByTestID(`selector-list tv1`).click() + cy.getByTestID('time-machine-submit-button').click() + cy.getByTestID('view-type--dropdown').click() + cy.getByTestID('view-type--line-plus-single-stat').click() + + //asserting default graph time format, saving + cy.getByTestID('cog-cell--button').click() + cy.getByTestID('dropdown--button').should('contain', timeFormatOriginal) + cy.getByTestID(`save-cell--button`).click() + + //changing graph time format + cy.getByTestID(`cell-context--toggle`).click() + cy.getByTestID(`cell-context--configure`).click() + cy.getByTestID('dropdown--button').should('contain', timeFormatOriginal) + cy.getByTestID('dropdown--button') + .contains(timeFormatOriginal) + .click() + cy.getByTestID('dropdown-item') + .contains(timeFormatNew) + .click() + cy.getByTestID('dropdown--button').should('contain', timeFormatNew) + cy.getByTestID(`save-cell--button`).click() + + //asserting the time format hasn't changed after saving the cell + cy.getByTestID(`cell-context--toggle`).click() + cy.getByTestID(`cell-context--configure`).click() + cy.getByTestID('dropdown--button').should('contain', timeFormatNew) + }) + + it('can sort values in a dashboard cell', () => { + const numLines = 360 + cy.writeData(lines(numLines)) + cy.get('@org').then(({id: orgID}: Organization) => { + cy.createDashboard(orgID).then(({body}) => { + cy.fixture('routes').then(({orgs}) => { + cy.visit(`${orgs}/${orgID}/dashboards/${body.id}`) + }) + }) + }) + + //creating new dashboard cell + cy.getByTestID('add-cell--button') + .click() + .then(() => { + cy.getByTestID(`selector-list m`) + .click() + .getByTestID('selector-list v') + .click() + .getByTestID(`selector-list tv1`) + .click() + .then(() => { + cy.getByTestID('time-machine-submit-button').click() + }) + }) + + //change to table graph type + cy.getByTestID('view-type--dropdown') + .click() + .then(() => { + cy.getByTestID('view-type--table').click() + }) + cy.getByTestID(`save-cell--button`).click() + + //assert sorting + cy.getByTestID(`cell Name this Cell`).then(() => { + cy.getByTestID('_value-table-header') + .should('have.class', 'table-graph-cell') + .click() + .should('have.class', 'table-graph-cell__sort-asc') + .click() + .should('have.class', 'table-graph-cell__sort-desc') + }) + }) }) diff --git a/cypress/e2e/explorer.test.ts b/cypress/e2e/explorer.test.ts index aa9cd08e2f..f31d664983 100644 --- a/cypress/e2e/explorer.test.ts +++ b/cypress/e2e/explorer.test.ts @@ -27,8 +27,62 @@ function getTimeMachineText() { .invoke('text') } -// NOTE: this crashes circle FOR NO REASON -describe.skip('DataExplorer', () => { +type GraphSnapshot = { + shouldBeSameAs: ( + other: GraphSnapshot, + same?: boolean, + part?: 'axes' | 'layer' | 'both' + ) => void + name: string +} + +const makeGraphSnapshot = (() => { + // local properties for makeGraphSnapshot function + let lastGraphSnapsotIndex = 0 + const getNameAxes = (name: string) => `${name}-axes` + const getNameLayer = (name: string) => `${name}-layer` + + return (): GraphSnapshot => { + // generate unique name for snapshot for saving as cy var + const name = `graph-snapshot-${lastGraphSnapsotIndex++}` + + // wait for drawing done + cy.wait(150) + cy.get('[data-testid|=giraffe-layer]') + .then($layer => ($layer[0] as HTMLCanvasElement).toDataURL('image/jpeg')) + .as(getNameLayer(name)) + + cy.getByTestID('giraffe-axes') + .then($axes => ($axes[0] as HTMLCanvasElement).toDataURL('image/jpeg')) + .as(getNameAxes(name)) + + return { + name, + shouldBeSameAs: ({name: nameOther}, same = true, part = 'both') => { + const assert = (str: any, str2: any, same: boolean) => { + if (same) expect(str).to.eq(str2) + else expect(str).to.not.eq(str2) + } + + if (part === 'both' || part === 'axes') + cy.get(`@${getNameAxes(name)}`).then(axes => { + cy.get(`@${getNameAxes(nameOther)}`).then(axesOther => { + assert(axes, axesOther, same) + }) + }) + + if (part === 'both' || part === 'layer') + cy.get(`@${getNameLayer(name)}`).then(layer => { + cy.get(`@${getNameLayer(nameOther)}`).then(layerOther => { + assert(layer, layerOther, same) + }) + }) + }, + } + } +})() + +describe('DataExplorer', () => { beforeEach(() => { cy.flush() @@ -86,9 +140,7 @@ describe.skip('DataExplorer', () => { it('should put input field in error status and stay in error status when input is invalid or empty', () => { cy.get('.view-options').within(() => { cy.getByTestID('auto-input').within(() => { - cy.getByTestID('input-field') - .click() - .type('{backspace}{backspace}') + cy.getByTestID('input-field').clear() cy.getByTestID('auto-input--custom').should( 'have.class', 'cf-select-group--option__active' @@ -104,8 +156,8 @@ describe.skip('DataExplorer', () => { cy.get('.view-options').within(() => { cy.getByTestID('auto-input').within(() => { cy.getByTestID('input-field') - .click() - .type('{backspace}{backspace}3') + .clear() + .type('3') cy.getByTestID('input-field--error').should('have.length', 0) }) }) @@ -147,7 +199,7 @@ describe.skip('DataExplorer', () => { cy.getByTestID('grid--column').within(() => { cy.getByTestID('bin-size-input') .clear() - .type('{backspace}{backspace}5') + .type('5') .getByTestID('bin-size-input--error') .should('have.length', 0) }) @@ -374,9 +426,6 @@ describe.skip('DataExplorer', () => { beforeEach(() => { cy.writeData([`${measurement} ${field}=0`, `${measurement} ${field}=1`]) - cy.getByTestID('selector-list _monitoring').click() - cy.wait(500) // wait for server to turn back on - cy.getByTestID('selector-list defbuck').click() }) it('can switch to and from script editor mode', () => { @@ -432,16 +481,21 @@ describe.skip('DataExplorer', () => { .click() }) - it('shows flux signatures and errors', () => { + it('shows flux errors', () => { cy.getByTestID('time-machine--bottom').then(() => { cy.getByTestID('flux-editor').within(() => { cy.get('textarea').type('foo |> bar', {force: true}) cy.get('.squiggly-error').should('be.visible') + }) + }) + }) - cy.get('textarea').type('{selectall} {backspace}', {force: true}) - + it('shows flux signatures', () => { + cy.getByTestID('time-machine--bottom').then(() => { + cy.getByTestID('flux-editor').within(() => { cy.get('textarea').type('from(', {force: true}) + cy.get('.signature').should('be.visible') }) }) @@ -682,8 +736,8 @@ describe.skip('DataExplorer', () => { }) }) - describe('visualize with 360 lines', () => { - const numLines = 360 + const numLines = 360 + describe(`visualize with ${numLines} lines`, () => { beforeEach(() => { // POST 360 lines to the server cy.writeData(lines(numLines)) @@ -799,6 +853,81 @@ describe.skip('DataExplorer', () => { cy.getByTestID('dropdown-y').contains('_time') }) + it('can zoom and unzoom horizontal axis', () => { + cy.getByTestID(`selector-list m`).click() + cy.getByTestID('selector-list v').click() + cy.getByTestID(`selector-list tv1`).click() + + cy.getByTestID('time-machine-submit-button').click() + + const snapshot = makeGraphSnapshot() + + cy.getByTestID('giraffe-layer-line').then(([canvas]) => { + const {width, height} = canvas + + cy.wrap(canvas).trigger('mousedown', {x: width / 3, y: height / 2}) + cy.wrap(canvas).trigger('mousemove', { + x: (width * 2) / 3, + y: height / 2, + }) + cy.wrap(canvas).trigger('mouseup', {force: true}) + }) + + const snapshot2 = makeGraphSnapshot() + snapshot.shouldBeSameAs(snapshot2, false) + + cy.getByTestID('giraffe-layer-line').dblclick({force: true}) + makeGraphSnapshot().shouldBeSameAs(snapshot) + }) + + it('can zoom and unzoom vertical axis', () => { + cy.getByTestID(`selector-list m`).click() + cy.getByTestID('selector-list v').click() + cy.getByTestID(`selector-list tv1`).click() + + cy.getByTestID('time-machine-submit-button').click() + + const snapshot = makeGraphSnapshot() + + cy.getByTestID('giraffe-layer-line').then(([canvas]) => { + const {width, height} = canvas + + cy.wrap(canvas).trigger('mousedown', {x: width / 2, y: height / 3}) + cy.wrap(canvas).trigger('mousemove', { + x: width / 2, + y: (height * 2) / 3, + }) + cy.wrap(canvas).trigger('mouseup', {force: true}) + }) + + const snapshot2 = makeGraphSnapshot() + snapshot.shouldBeSameAs(snapshot2, false) + + cy.getByTestID('giraffe-layer-line').dblclick({force: true}) + makeGraphSnapshot().shouldBeSameAs(snapshot) + }) + + it('can hover over graph to show tooltip', () => { + // build the query to return data from beforeEach + cy.getByTestID(`selector-list m`).click() + cy.getByTestID('selector-list v').click() + cy.getByTestID(`selector-list tv1`).click() + + cy.getByTestID('time-machine-submit-button').click() + + cy.getByTestID('giraffe-tooltip').should('not.visible') + cy.getByTestID('giraffe-layer-line') + .click() + .trigger('mouseover') + + cy.wait(100) + cy.getByTestID('giraffe-layer-line').trigger('mousemove', {force: true}) + + cy.getByTestID('giraffe-tooltip').should('visible') + cy.getByTestID('giraffe-layer-line').trigger('mouseout', {force: true}) + cy.getByTestID('giraffe-tooltip').should('not.visible') + }) + it('can view table data & sort values numerically', () => { // build the query to return data from beforeEach cy.getByTestID(`selector-list m`).click() @@ -904,26 +1033,295 @@ describe.skip('DataExplorer', () => { }) }) + describe('refresh', () => { + beforeEach(() => { + cy.writeData(lines(10)) + cy.getByTestID(`selector-list m`).click() + cy.getByTestID('time-machine-submit-button').click() + + // select short time period to ensure graph changes after short time + cy.getByTestID('timerange-dropdown').click() + cy.getByTestID('dropdown-item-past5m').click() + }) + + it('manual refresh', () => { + const snapshot = makeGraphSnapshot() + + // graph will slightly move + cy.wait(200) + cy.getByTestID('autorefresh-dropdown-refresh').click() + makeGraphSnapshot().shouldBeSameAs(snapshot, false) + }) + + it('auto refresh', () => { + const snapshot = makeGraphSnapshot() + cy.getByTestID('autorefresh-dropdown--button').click() + cy.getByTestID('auto-refresh-5s').click() + + cy.wait(3_000) + makeGraphSnapshot().shouldBeSameAs(snapshot) + + cy.wait(3_000) + const snapshot2 = makeGraphSnapshot() + snapshot2.shouldBeSameAs(snapshot, false) + + cy.getByTestID('autorefresh-dropdown-refresh').should('not.be.visible') + cy.getByTestID('autorefresh-dropdown--button') + .should('contain.text', '5s') + .click() + cy.getByTestID('auto-refresh-paused').click() + cy.getByTestID('autorefresh-dropdown-refresh').should('be.visible') + + // wait if graph changes after another 6s when autorefresh is paused + cy.wait(6_000) + makeGraphSnapshot().shouldBeSameAs(snapshot2) + }) + }) + describe('saving', () => { beforeEach(() => { - cy.fixture('routes').then(({orgs, explorer}) => { - cy.get('@org').then(({id}) => { - cy.visit(`${orgs}/${id}${explorer}/save`) + cy.writeData(lines(10)) + }) + + it('can open/close save as dialog and navigate inside', () => { + // open save as + cy.getByTestID('overlay--container').should('not.be.visible') + cy.getByTestID('save-query-as').click() + cy.getByTestID('overlay--container').should('be.visible') + + // test all tabs + cy.getByTestID('task--radio-button').click() + cy.getByTestID('task-form-name').should('be.visible') + cy.getByTestID('variable--radio-button').click() + cy.getByTestID('flux-editor').should('be.visible') + cy.getByTestID('cell--radio-button').click() + cy.getByTestID('save-as-dashboard-cell--dropdown').should('be.visible') + + // close save as + cy.getByTestID('save-as-overlay--header').within(() => { + cy.get('button').click() + }) + cy.getByTestID('overlay--container').should('not.be.visible') + }) + + describe('as dashboard cell', () => { + const dashboardNames = ['dashboard 1', 'board 2', 'board 3'] + const cellName = '📊 graph 1' + const dashboardCreateName = '📋 board' + + beforeEach(() => { + cy.get('@org').then(({id: orgID}: Organization) => { + dashboardNames.forEach((d, i) => { + cy.createDashboard(orgID, d).then(({body}) => { + cy.wrap(body.id).as(`dasboard${i}-id`) + }) + }) + }) + + // setup query for saving and open dasboard dialog + cy.getByTestID(`selector-list m`).click() + cy.getByTestID('save-query-as').click() + cy.getByTestID('cell--radio-button').click() + }) + + it('can save as cell into multiple dashboards', () => { + // input dashboards and cell name + cy.getByTestID('save-as-dashboard-cell--dropdown').click() + cy.getByTestID('save-as-dashboard-cell--dropdown-menu').within(() => { + dashboardNames.forEach(d => { + cy.contains(d).click() + }) + }) + cy.getByTestID('save-as-dashboard-cell--dropdown').click() + cy.getByTestID('save-as-dashboard-cell--cell-name').type(cellName) + + cy.getByTestID('save-as-dashboard-cell--submit').click() + + // ensure cell exists at dashboards + cy.get('@org').then(({id: orgID}: Organization) => { + cy.fixture('routes').then(({orgs}) => { + dashboardNames.forEach((_, i) => { + cy.get(`@dasboard${i}-id`).then(id => { + cy.visit(`${orgs}/${orgID}/dashboards/${id}`) + cy.getByTestID(`cell ${cellName}`).should('exist') + }) + }) + }) + }) + }) + + it('can create new dasboard as saving target', () => { + // select and input new dashboard name and cell name + cy.getByTestID('save-as-dashboard-cell--dropdown').click() + cy.getByTestID('save-as-dashboard-cell--create-new-dash').click() + cy.getByTestID('save-as-dashboard-cell--dropdown').click() + cy.getByTestID('save-as-dashboard-cell--dashboard-name') + .should('be.visible') + .clear() + .type(dashboardCreateName) + cy.getByTestID('save-as-dashboard-cell--cell-name').type(cellName) + + cy.getByTestID('save-as-dashboard-cell--submit').click() + + // wait some time for save + cy.wait(100) + // ensure dasboard created with cell + cy.get('@org').then(({id: orgID}: Organization) => { + cy.fixture('routes').then(({orgs}) => { + cy.visit(`${orgs}/${orgID}/dashboards/`) + cy.getByTestID('dashboard-card--name') + .contains(dashboardCreateName) + .should('exist') + .click() + cy.getByTestID(`cell ${cellName}`).should('exist') + }) }) }) }) describe('as a task', () => { + const bucketName = 'bucket 2' + const taskName = '☑ task' + const offset = '30m' + const timeEvery = '50h10m5s' + const timeCron = '0 0 12 * * TUE,FRI,SUN *' + // for strong typings + const cron = 'cron' + const every = 'every' + const both: ('cron' | 'every')[] = [cron, every] + + const fillForm = ( + type: 'cron' | 'every', + texts: {time?: string; offset?: string; taskName?: string} + ) => { + const checkAndType = (target: string, text: string | undefined) => { + cy.getByTestID(target).clear() + if (text) cy.getByTestID(target).type(text) + } + const {offset, taskName, time} = texts + + cy.getByTestID(`task-card-${type}-btn`).click() + checkAndType('task-form-name', taskName) + checkAndType('task-form-schedule-input', time) + checkAndType('task-form-offset-input', offset) + } + + const visitTasks = () => { + cy.fixture('routes').then(({orgs}) => { + cy.get('@org').then(({id}: Organization) => { + cy.visit(`${orgs}/${id}/tasks`) + }) + }) + } + beforeEach(() => { + cy.get('@org').then(({id, name}: Organization) => { + cy.createBucket(id, name, bucketName) + }) + + cy.getByTestID('selector-list defbuck').click() + cy.getByTestID('nav-item-data-explorer').click({force: true}) + cy.getByTestID(`selector-list m`).click() + cy.getByTestID('save-query-as').click({force: true}) cy.getByTestID('task--radio-button').click() }) - it('should autoselect the first bucket', () => { - cy.getByTestID('task-options-bucket-dropdown--button').within(() => { - cy.get('span.cf-dropdown--selected').then(elem => { - expect(elem.text()).to.include('defbuck') + // TODO: enable when problem with switching cron/every is fixed + it.skip('should enable/disable submit based on inputs', () => { + both.forEach(type => { + const time = type === 'every' ? timeEvery : timeCron + cy.getByTestID('task-form-save').should('be.disabled') + fillForm(type, {}) + cy.getByTestID('task-form-save').should('be.disabled') + fillForm(type, {time, taskName}) + cy.getByTestID('task-form-save').should('be.enabled') + fillForm(type, {taskName, offset}) + cy.getByTestID('task-form-save').should('be.disabled') + fillForm(type, {time, offset}) + cy.getByTestID('task-form-save').should('be.disabled') + }) + }) + + both.forEach(type => + [true, false].forEach(withOffset => { + it(`can create ${type} task with${ + withOffset ? '' : 'out' + } offset`, () => { + const time = type === 'every' ? timeEvery : timeCron + fillForm(type, {time, taskName, ...(withOffset ? {offset} : {})}) + cy.getByTestID('task-form-save').click() + + visitTasks() + + cy.getByTestID('task-card--name') + .should('exist') + .click() + cy.getByTestID('task-form-schedule-input').should( + 'have.value', + time + ) + cy.getByTestID('task-form-offset-input').should( + 'have.value', + withOffset ? offset : '' + ) + }) + }) + ) + + it('can select buckets', () => { + fillForm('every', {time: timeEvery, taskName}) + + cy.getByTestID('task-options-bucket-dropdown--button').click() + cy.getByTestID('dropdown-item') + .contains(bucketName) + .click() + cy.getByTestID('task-options-bucket-dropdown--button') + .contains(bucketName) + .should('exist') + + cy.getByTestID('task-options-bucket-dropdown--button').click() + cy.getByTestID('dropdown-item') + .contains('defbuck') + .click() + cy.getByTestID('task-options-bucket-dropdown--button') + .contains('defbuck') + .should('exist') + + cy.getByTestID('task-form-save').click() + }) + }) + + describe('as variable', () => { + const variableName = 'var 1' + + const visitVariables = () => { + cy.fixture('routes').then(({orgs}) => { + cy.get('@org').then(({id}: Organization) => { + cy.visit(`${orgs}/${id}/settings/variables`) }) }) + } + + beforeEach(() => { + cy.getByTestID('nav-item-data-explorer').click({force: true}) + cy.getByTestID(`selector-list m`).click() + cy.getByTestID('save-query-as').click({force: true}) + cy.getByTestID('variable--radio-button').click() + }) + + it('can save and enable/disable submit button', () => { + cy.getByTestID('variable-form-save').should('be.disabled') + cy.getByTestID('variable-name-input').type(variableName) + cy.getByTestID('variable-form-save').should('be.enabled') + cy.getByTestID('variable-name-input').clear() + cy.getByTestID('variable-form-save').should('be.disabled') + cy.getByTestID('variable-name-input').type(variableName) + cy.getByTestID('variable-form-save').should('be.enabled') + + cy.getByTestID('variable-form-save').click() + + visitVariables() + cy.getByTestID(`variable-card--name ${variableName}`).should('exist') }) }) }) diff --git a/cypress/e2e/loadDataSources.test.ts b/cypress/e2e/loadDataSources.test.ts index a3da1001ff..310ef10fc0 100644 --- a/cypress/e2e/loadDataSources.test.ts +++ b/cypress/e2e/loadDataSources.test.ts @@ -1,4 +1,4 @@ -describe.skip('Load Data Sources', () => { +describe('Load Data Sources', () => { beforeEach(() => { cy.flush() diff --git a/cypress/e2e/login.test.ts b/cypress/e2e/login.test.ts index 5e73f5b5fc..6778551f89 100644 --- a/cypress/e2e/login.test.ts +++ b/cypress/e2e/login.test.ts @@ -1,7 +1,17 @@ +interface LoginUser { + username: string + password: string +} + describe('The Login Page', () => { + let user: LoginUser beforeEach(() => { cy.flush() + cy.fixture('user').then(u => { + user = u + }) + cy.setupUser().then(({body}) => { cy.wrap(body.org.id).as('orgID') }) @@ -9,12 +19,10 @@ describe('The Login Page', () => { cy.visit('/') }) - // NOTE: we aren't currently loading the login page - // for dex - it.skip('can login and logout', () => { - cy.get('#username').type(Cypress.env('username')) - cy.get('#password').type(Cypress.env('password')) - cy.get('#submit-login').click() + it('can login and logout', () => { + cy.getByInputName('username').type(user.username) + cy.getByInputName('password').type(user.password) + cy.get('button[type=submit]').click() cy.getByTestID('tree-nav').should('exist') @@ -31,33 +39,49 @@ describe('The Login Page', () => { cy.getByTestID('signin-page--content').should('exist') }) - // NOTE: we aren't currently loading the login page - // for dex - describe.skip('login failure', () => { + it('can logout from homepage', () => { + cy.flush() + cy.signin() + cy.visit('/') + + cy.getByTestID('logout--button').click() + + cy.getByTestID('signin-page--content').should('exist') + + // try to access a protected route + cy.get('@orgID').then(orgID => { + cy.visit(`/orgs/${orgID}`) + }) + + // assert that user is routed to signin + cy.getByTestID('signin-page--content').should('exist') + }) + + describe('login failure', () => { it('if username is not present', () => { - cy.get('#password').type(Cypress.env('password')) - cy.get('#submit-login').click() + cy.getByInputName('password').type(user.password) + cy.get('button[type=submit]').click() cy.getByTestID('notification-error').should('exist') }) it('if password is not present', () => { - cy.get('#username').type(Cypress.env('username')) - cy.get('#submit-login').click() + cy.getByInputName('username').type(user.username) + cy.get('button[type=submit]').click() cy.getByTestID('notification-error').should('exist') }) it('if username is incorrect', () => { cy.getByInputName('username').type('not-a-user') - cy.getByInputName('password').type(Cypress.env('password')) + cy.getByInputName('password').type(user.password) cy.get('button[type=submit]').click() cy.getByTestID('notification-error').should('exist') }) it('if password is incorrect', () => { - cy.getByInputName('username').type(Cypress.env('username')) + cy.getByInputName('username').type(user.username) cy.getByInputName('password').type('not-a-password') cy.get('button[type=submit]').click() diff --git a/cypress/e2e/nav.test.ts b/cypress/e2e/nav.test.ts index b3f1069c57..0fa7747efe 100644 --- a/cypress/e2e/nav.test.ts +++ b/cypress/e2e/nav.test.ts @@ -9,7 +9,7 @@ describe('navigation', () => { cy.visit('/') }) - it('can navigate to each page', () => { + it('can navigate to each page from left nav', () => { // Load Data Page cy.getByTestID('nav-item-load-data').click() cy.getByTestID('load-data--header').should('exist') @@ -99,7 +99,62 @@ describe('navigation', () => { cy.getByTestID('user-nav').click() cy.getByTestID('user-nav-item-logout').click() cy.getByTestID('signin-page').should('exist') - + \**/ }) + + it('can navigate to pages from homepage', () => + ['load-data', 'dashboards', 'alerting'].forEach(card => { + cy.getByTestID('tree-nav--header').click() + cy.getByTestID(`getting-started--${card}--button`).click() + cy.url().should('contain', card) + })) + + const exploreTabs = (tabs: string[]) => { + tabs.forEach(tab => { + cy.getByTestID(`${tab}--tab`).click() + cy.url().should('contain', tab) + }) + } + + it('can navigate in tabs of data page', () => { + cy.getByTestID('nav-item-load-data').click() + exploreTabs(['buckets', 'telegrafs', 'scrapers', 'tokens', 'sources']) + }) + + it('can navigate in tabs of settings page', () => { + cy.getByTestID('nav-item-settings').click() + exploreTabs(['templates', 'labels', 'variables']) + }) + + it('can navigate in tabs of collapsed alerts page', () => { + cy.getByTestID('nav-item-alerting').click() + ;['checks', 'endpoints', 'rules'].forEach(tab => { + cy.getByTestID(`alerting-tab--${tab}`).click() + cy.getByTestID(`alerting-tab--${tab}--input`).should('to.be', 'checked') + }) + }) + + it('can navigate in tabs from maximized left tree nav', () => { + // TODO: check if nav is already maximized + cy.get('.cf-tree-nav--toggle').click() + ;[ + 'sources', + 'buckets', + 'telegrafs', + 'scrapers', + 'tokens', + 'history', + 'variables', + 'templates', + 'labels', + ].forEach(x => { + cy.getByTestID(`nav-subitem-${x}`) + .last() + .click() + cy.url().should('contain', x) + }) + + cy.get('.cf-tree-nav--toggle').click() + }) }) diff --git a/cypress/e2e/notificationEndpoints.test.ts b/cypress/e2e/notificationEndpoints.test.ts index cf30482123..75b078fb4e 100644 --- a/cypress/e2e/notificationEndpoints.test.ts +++ b/cypress/e2e/notificationEndpoints.test.ts @@ -5,7 +5,7 @@ import { } from '../../src/types' // skipping these tests until we have a local vault instance working -describe.skip('Notification Endpoints', () => { +describe('Notification Endpoints', () => { const endpoint: NotificationEndpoint = { orgID: '', name: 'Pre-Created Endpoint', diff --git a/cypress/e2e/notificationRules.test.ts b/cypress/e2e/notificationRules.test.ts index 67ac5b9a84..b5c6e4ba1b 100644 --- a/cypress/e2e/notificationRules.test.ts +++ b/cypress/e2e/notificationRules.test.ts @@ -1,7 +1,7 @@ import {SlackNotificationEndpoint, Organization} from '../../src/types' // skipping these tests until we have a local vault instance running -describe.skip('NotificationRules', () => { +describe('NotificationRules', () => { const name1 = 'Slack 1' const name2 = 'Slack 2' const name3 = 'Slack 3' diff --git a/cypress/e2e/onboarding.test.ts b/cypress/e2e/onboarding.test.ts index 0dc1156fda..d6c2e48dc3 100644 --- a/cypress/e2e/onboarding.test.ts +++ b/cypress/e2e/onboarding.test.ts @@ -11,7 +11,7 @@ describe('Onboarding Redirect', () => { }) // NOTE: important to test for OSS, not so much for cloud -describe.skip('Onboarding', () => { +describe('Onboarding', () => { beforeEach(() => { cy.flush() diff --git a/cypress/e2e/orgs.test.ts b/cypress/e2e/orgs.test.ts index 6dcd7d8116..3827a72945 100644 --- a/cypress/e2e/orgs.test.ts +++ b/cypress/e2e/orgs.test.ts @@ -1,7 +1,7 @@ const secondOrg = 'Second Org' // NOTE: this is dying for no reason in circleci -describe.skip('Orgs', () => { +describe('Orgs', () => { beforeEach(() => { cy.flush() }) diff --git a/cypress/e2e/scrapers.test.ts b/cypress/e2e/scrapers.test.ts index 50713a597f..788808b8a3 100644 --- a/cypress/e2e/scrapers.test.ts +++ b/cypress/e2e/scrapers.test.ts @@ -5,7 +5,7 @@ const PAGE_LOAD_SLA = 10000 // NOTE // this isn't supported in cloud mode -describe.skip('Scrapers', () => { +describe('Scrapers', () => { beforeEach(() => { cy.flush() @@ -109,4 +109,228 @@ describe.skip('Scrapers', () => { }) }) }) + + describe('Filtering and sorting', () => { + beforeEach(() => { + const type = 'TypeTest' + + cy.get('@org').then((org: Organization) => { + cy.get('@bucket').then((bucket: Bucket) => { + cy.createScraper( + 'Michigan', + 'http://Michigan.com', + type, + org.id, + bucket.id + ) + cy.createScraper( + 'Minnesota', + 'http://Minnesota.com', + type, + org.id, + bucket.id + ) + cy.createScraper('Iowa', 'http://Iowa.com', type, org.id, bucket.id) + cy.createScraper( + 'Nebraska', + 'http://Nebraska.com', + type, + org.id, + bucket.id + ) + }) + }) + + cy.fixture('routes').then(({orgs}) => { + cy.get('@org').then(({id}: Organization) => { + cy.visit(`${orgs}/${id}/load-data/scrapers`) + }) + }) + cy.get('[data-testid="resource-list--body"]', {timeout: PAGE_LOAD_SLA}) + }) + + it('can filter scrapers', () => { + //filter scrapers starting with "Mi" + cy.getByTestID('resource-card').should('have.length', 4) + cy.getByTestID('search-widget') + .type('Mi') + .then(() => { + cy.getByTestID('resource-card').should('have.length', 2) + cy.getByTestID('resource-card').should('contain', 'Michigan') + cy.getByTestID('resource-card').should('contain', 'Minnesota') + }) + + //clear and assert all scrapers are visible + cy.getByTestID('search-widget') + .clear() + .then(() => { + cy.getByTestID('resource-card').should('have.length', 4) + }) + + //filter non-existing scrapers + cy.getByTestID('search-widget') + .type('Illinois') + .then(() => { + cy.getByTestID('empty-state').should('exist') + }) + }) + + it('can sort scrapers', () => { + //sort by name - descending + cy.getByTestID('resource-sorter--button') + .click() + .then(() => { + cy.getByTestID('resource-sorter--name-desc') + .click() + .then(() => { + cy.getByTestID('resource-card') + .eq(0) + .should('contain', 'Nebraska') + cy.getByTestID('resource-card') + .eq(3) + .should('contain', 'Iowa') + }) + }) + + //sort by name - ascending + cy.getByTestID('resource-sorter--button') + .click() + .then(() => { + cy.getByTestID('resource-sorter--name-asc') + .click() + .then(() => { + cy.getByTestID('resource-card') + .eq(0) + .should('contain', 'Iowa') + cy.getByTestID('resource-card') + .eq(3) + .should('contain', 'Nebraska') + }) + }) + + //sort by url - descending + cy.getByTestID('resource-sorter--button') + .click() + .then(() => { + cy.getByTestID('resource-sorter--url-desc') + .click() + .then(() => { + cy.getByTestID('resource-card') + .eq(0) + .should('contain', 'Nebraska') + cy.getByTestID('resource-card') + .eq(3) + .should('contain', 'Iowa') + }) + }) + + //sort by url - ascending + cy.getByTestID('resource-sorter--button') + .click() + .then(() => { + cy.getByTestID('resource-sorter--url-asc') + .click() + .then(() => { + cy.getByTestID('resource-card') + .eq(0) + .should('contain', 'Iowa') + cy.getByTestID('resource-card') + .eq(3) + .should('contain', 'Nebraska') + }) + }) + + //create new buckets to assert sorting by buckets + cy.getByTestID('tabs--tab') + .contains('Buckets') + .click() + cy.getByTestID('Create Bucket') + .click() + .then(() => { + cy.getByTestID('bucket-form-name').type('Arkansas') + cy.getByTestID('bucket-form-submit').click() + }) + cy.getByTestID('Create Bucket') + .click() + .then(() => { + cy.getByTestID('bucket-form-name').type('Wisconsin') + cy.getByTestID('bucket-form-submit').click() + }) + + cy.getByTestID('tabs--tab') + .contains('Scrapers') + .click() + + //create new scrapers + cy.getByTestID('create-scraper-button-header').click() + cy.getByTitle('Name') + .clear() + .type('Arkansas') + cy.getByTestID('bucket-dropdown--button') + .click() + .then(() => { + cy.getByTestID('dropdown-item') + .contains('Arkansas') + .click() + .then(() => { + cy.getByTestID('create-scraper--submit') + .click() + .then(() => { + cy.getByTestID('resource-card').should('contain', 'Arkansas') + }) + }) + }) + + cy.getByTestID('create-scraper-button-header').click() + cy.getByTitle('Name') + .clear() + .type('Wisconsin') + cy.getByTestID('bucket-dropdown--button') + .click() + .then(() => { + cy.getByTestID('dropdown-item') + .contains('Wisconsin') + .click() + .then(() => { + cy.getByTestID('create-scraper--submit') + .click() + .then(() => { + cy.getByTestID('resource-card').should('contain', 'Wisconsin') + }) + }) + }) + + //sort by buckets - descending + cy.getByTestID('resource-sorter--button') + .click() + .then(() => { + cy.getByTestID('resource-sorter--bucket-desc') + .click() + .then(() => { + cy.getByTestID('resource-card') + .eq(0) + .should('contain', 'Wisconsin') + cy.getByTestID('resource-card') + .eq(5) + .should('contain', 'Arkansas') + }) + }) + + //sort by buckets - ascending + cy.getByTestID('resource-sorter--button') + .click() + .then(() => { + cy.getByTestID('resource-sorter--bucket-asc') + .click() + .then(() => { + cy.getByTestID('resource-card') + .eq(0) + .should('contain', 'Arkansas') + cy.getByTestID('resource-card') + .eq(5) + .should('contain', 'Wisconsin') + }) + }) + }) + }) }) diff --git a/cypress/e2e/tasks.test.ts b/cypress/e2e/tasks.test.ts index ec0b02b0ac..d952d4b6dd 100644 --- a/cypress/e2e/tasks.test.ts +++ b/cypress/e2e/tasks.test.ts @@ -110,6 +110,95 @@ http.post( ) }) + it('can create a cron task', () => { + cy.getByTestID('empty-tasks-list').within(() => { + cy.getByTestID('add-resource-dropdown--button').click() + }) + + cy.getByTestID('add-resource-dropdown--new').click() + + cy.getByTestID('flux-editor').within(() => { + cy.get('textarea.inputarea') + .click() + .type('from(bucket: "defbuck")\n' + '\t|> range(start: -2m)', { + force: true, + delay: 2, + }) + }) + + cy.getByTestID('task-form-name') + .click() + .type('Cron task test') + .then(() => { + cy.getByTestID('task-card-cron-btn').click() + cy.getByTestID('task-form-schedule-input') + .click() + .type('0 4 8-14 * *') + cy.getByTestID('task-form-offset-input') + .click() + .type('10m') + }) + + cy.getByTestID('task-save-btn').click() + + cy.getByTestID('task-card') + .contains('Cron task test') + .get('.cf-resource-meta--item') + .contains('Scheduled to run 0 4 8-14 * *') + + cy.getByTestID('task-card') + .contains('Cron task test') + .click() + .then(() => { + cy.getByTestID('task-form-schedule-input').should( + 'have.value', + '0 4 8-14 * *' + ) + }) + }) + + //skip until this issue is resolved + //https://github.com/influxdata/ui/issues/96 + it.skip('can create a task with an option parameter', () => { + cy.getByTestID('empty-tasks-list').within(() => { + cy.getByTestID('add-resource-dropdown--button') + .click() + .then(() => { + cy.getByTestID('add-resource-dropdown--new').click() + }) + }) + + cy.getByTestID('flux-editor').within(() => { + cy.get('textarea.inputarea') + .click() + .type( + 'option task = \n' + + '{\n' + + 'name: "Option Test", \n' + + 'every: 24h, \n' + + 'offset: 20m\n' + + '}\n' + + 'from(bucket: "defbuck")\n' + + '\t|> range(start: -2m)' + ) + }) + + cy.getByTestID('task-form-name') + .click() + .type('Option Test') + .then(() => { + cy.getByTestID('task-form-schedule-input') + .click() + .type('24h') + cy.getByTestID('task-form-offset-input') + .click() + .type('20m') + }) + + cy.getByTestID('task-save-btn').click() + cy.getByTestID('notification-success').should('exist') + }) + describe('When tasks already exist', () => { beforeEach(() => { cy.get('@org').then(({id}: Organization) => { @@ -186,6 +275,155 @@ http.post( }) }) }) + + //skipping until this issue is resolved + //https://github.com/influxdata/influxdb/issues/18478 + it.skip('can clone a task and activate just the cloned one', () => { + cy.getByTestID('task-card').then(() => { + cy.get('.context-menu--container') + .eq(1) + .click() + .then(() => { + cy.getByTestID('context-menu-item') + .contains('Clone') + .click() + }) + }) + + cy.getByTestID('task-card--slide-toggle') + .eq(0) + .should('have.class', 'active') + .then(() => { + cy.getByTestID('task-card--slide-toggle') + .eq(0) + .click() + .then(() => { + cy.getByTestID('task-card--slide-toggle') + .eq(0) + .should('not.have.class', 'active') + cy.getByTestID('task-card--slide-toggle') + .eq(1) + .should('have.class', 'active') + }) + }) + }) + + it('can clone a task and edit it', () => { + //clone a task + cy.getByTestID('task-card') + .first() + .trigger('mouseover') + .then(() => { + cy.get('.context-menu--container') + .eq(1) + .within(() => { + cy.getByTestID('context-menu') + .click() + .then(() => { + cy.getByTestID('context-menu-item') + .contains('Clone') + .click() + }) + }) + }) + + cy.getByTestID('task-card').should('have.length', 2) + + //assert the values of the task and change them + cy.getByTestID('task-card--name') + .eq(1) + .click() + .then(() => { + cy.getByTestID('task-form-name') + .should('have.value', '🦄ask') + .then(() => { + cy.getByTestID('task-form-name') + .click() + .clear() + .type('Copy task test') + .then(() => { + cy.getByTestID('task-form-schedule-input') + .should('have.value', '24h') + .clear() + .type('12h') + .should('have.value', '12h') + cy.getByTestID('task-form-offset-input') + .should('have.value', '20m') + .clear() + .type('10m') + .should('have.value', '10m') + cy.getByTestID('task-save-btn').click() + }) + }) + }) + + //assert changed task name + cy.getByTestID('task-card--name').contains('Copy task test') + }) + + //skip until this issue is resolved + //https://github.com/influxdata/ui/issues/97 + it.skip('can add a comment into a task', () => { + cy.getByTestID('task-card--name') + .first() + .click() + + //assert textarea and write a comment + cy.getByTestID('flux-editor').within(() => { + cy.get('textarea.inputarea') + .should( + 'have.value', + 'option task = {\n' + + ' name: "🦄ask",\n' + + ' every: 24h,\n' + + ' offset: 20m\n' + + ' }\n' + + ' from(bucket: "defbuck")\n' + + ' |> range(start: -2m)' + ) + .click() + .focused() + .type('{end} {enter} //this is a test comment') + .then(() => { + cy.get('textarea.inputarea').should( + 'have.value', + 'option task = {\n' + + ' name: "🦄ask",\n' + + ' every: 24h,\n' + + ' offset: 20m\n' + + ' }\n' + + ' from(bucket: "defbuck")\n' + + ' |> range(start: -2m) \n' + + ' //this is a test comment' + ) + }) + }) + + //save and assert notification + cy.getByTestID('task-save-btn') + .click() + .then(() => { + cy.getByTestID('notification-success').contains( + 'Task was updated successfully' + ) + }) + + cy.getByTestID('task-card--name') + .first() + .click() + + //assert the comment + cy.getByTestID('flux-editor').within(() => { + cy.get('textarea.inputarea').should( + 'have.value', + 'option task = {name: "🦄ask", every: 24h, offset: 20m}\n' + + '\n' + + 'from(bucket: "defbuck")\n' + + '\t|> range(start: -2m)\n' + + ' //this is a test comment' + ) + }) + }) }) describe('Searching and filtering', () => { diff --git a/cypress/e2e/telegrafs.test.ts b/cypress/e2e/telegrafs.test.ts index 4376031d02..11b1fdb6cb 100644 --- a/cypress/e2e/telegrafs.test.ts +++ b/cypress/e2e/telegrafs.test.ts @@ -27,7 +27,9 @@ describe('Collectors', () => { const configDescription = 'This is a new config testing' cy.getByTestID('table-row').should('have.length', 0) - cy.contains('Create Configuration').click() + cy.getByTestID('resource-list--body').within(() => { + cy.contains('Create Configuration').click() + }) cy.getByTestID('overlay--container').within(() => { cy.getByTestID('telegraf-plugins--System').click() cy.getByTestID('next').click() @@ -54,6 +56,198 @@ describe('Collectors', () => { .should('have.length', 1) .and('contain', newConfig) .and('contain', Cypress.env('bucket')) + + //test the Create Configuration overlay dismiss button + cy.contains('Create Configuration').click() + cy.getByTestID('overlay--container') + .should('exist') + .then(() => { + cy.get('.cf-overlay--dismiss') + .click() + .then(() => { + cy.getByTestID('overlay--container').should('not.exist') + }) + }) + }) + + it('can create a telegraf config with every plugin', () => { + //docker plugin configuration + const dockerConf = 'Docker Config' + const dockerConfDescription = 'This is a docker config description' + + //open Create Configuration popup and select plugin + cy.getByTestID('table-row').should('have.length', 0) + cy.contains('Create Configuration').click() + cy.getByTestID('telegraf-plugins--Docker').click() + cy.getByTestID('next').click() + + //assert plugin and fill out inputs + cy.get('.side-bar--container').within(() => { + cy.get('.side-bar--tab').should('contain', 'docker') + }) + cy.getByInputName('name') + .clear() + .type(dockerConf) + cy.getByInputName('description') + .clear() + .type(dockerConfDescription) + cy.getByTestID('next').click() + + //assert notification + cy.getByTestID('notification-success').should( + 'contain', + 'Your configurations have been saved' + ) + + //click the Listen for Data button and assert the result + cy.getByTestID('streaming').within(() => { + cy.get('.cf-button') + .contains('Listen for Data') + .click() + }) + cy.getByTestID('streaming').should('contain', 'Connection Found!') + cy.getByTestID('next').click() + + //assert the created telegraf card + cy.fixture('user').then(() => { + cy.getByTestID('resource-card') + .should('have.length', 1) + .and('contain', dockerConf) + .and('contain', dockerConfDescription) + }) + + //kubernetes plugin configuration + const kubernetesConf = 'Kubernetes Config' + const kubernetesConfDescription = + 'This is a kubernetes config description' + + //open Create Configuration popup and select plugin + cy.contains('Create Configuration').click() + cy.getByTestID('telegraf-plugins--Kubernetes').click() + cy.getByTestID('next').click() + + //assert plugin and fill out inputs + cy.get('.side-bar--container').within(() => { + cy.get('.side-bar--tab').should('contain', 'kubernetes') + }) + cy.getByInputName('name') + .clear() + .type(kubernetesConf) + cy.getByInputName('description') + .clear() + .type(kubernetesConfDescription) + cy.getByTestID('next').click() + + //assert notification + cy.getByTestID('notification-success').should( + 'contain', + 'Your configurations have been saved' + ) + + //click the Listen for Data button and assert the result + cy.getByTestID('streaming').within(() => { + cy.get('.cf-button') + .contains('Listen for Data') + .click() + }) + cy.getByTestID('streaming').should('contain', 'Connection Found!') + cy.getByTestID('next').click() + + //assert the created telegraf card + cy.fixture('user').then(() => { + cy.getByTestID('resource-card') + .should('have.length', 2) + .and('contain', kubernetesConf) + .and('contain', kubernetesConfDescription) + }) + + //Nginx plugin configuration + const nginxConf = 'NGINX Config' + const nginxConfDescription = 'This is a nginx config description' + + //open Create Configuration popup and select plugin + cy.contains('Create Configuration').click() + cy.getByTestID('telegraf-plugins--NGINX').click() + cy.getByTestID('next').click() + + //assert plugin and fill out inputs + cy.get('.side-bar--container').within(() => { + cy.get('.side-bar--tab').should('contain', 'nginx') + }) + cy.getByInputName('name') + .clear() + .type(nginxConf) + cy.getByInputName('description') + .clear() + .type(nginxConfDescription) + cy.getByTestID('next').click() + + //assert notification + cy.getByTestID('notification-success').should( + 'contain', + 'Your configurations have been saved' + ) + + //click the Listen for Data button and assert the result + cy.getByTestID('streaming').within(() => { + cy.get('.cf-button') + .contains('Listen for Data') + .click() + }) + cy.getByTestID('streaming').should('contain', 'Connection Found!') + cy.getByTestID('next').click() + + //assert the created telegraf card + cy.fixture('user').then(() => { + cy.getByTestID('resource-card') + .should('have.length', 3) + .and('contain', nginxConf) + .and('contain', nginxConfDescription) + }) + + //Redis plugin configuration + const redisConf = 'Redis Config' + const redisConfDescription = 'This is a redis config description' + + //open Create Configuration popup and select plugin + cy.contains('Create Configuration').click() + cy.getByTestID('telegraf-plugins--Redis').click() + cy.getByTestID('next').click() + + //assert plugin and fill out inputs + cy.get('.side-bar--container').within(() => { + cy.get('.side-bar--tab').should('contain', 'redis') + }) + cy.getByInputName('name') + .clear() + .type(redisConf) + cy.getByInputName('description') + .clear() + .type(redisConfDescription) + cy.getByTestID('next').click() + + //assert notification + cy.getByTestID('notification-success').should( + 'contain', + 'Your configurations have been saved' + ) + + //click the Listen for Data button and assert the result + cy.getByTestID('streaming').within(() => { + cy.get('.cf-button') + .contains('Listen for Data') + .click() + }) + cy.getByTestID('streaming').should('contain', 'Connection Found!') + cy.getByTestID('next').click() + + //assert the created telegraf card + cy.fixture('user').then(() => { + cy.getByTestID('resource-card') + .should('have.length', 4) + .and('contain', redisConf) + .and('contain', redisConfDescription) + }) }) it('allows the user to view just the output', () => { @@ -138,6 +332,26 @@ describe('Collectors', () => { cy.getByTestID('resource-card').should('have.length', 1) }) + it('can edit configuration description', () => { + const newConfDesc = 'This is an edited description' + + //click on the description and edit it + cy.get('.cf-resource-description--preview') + .trigger('mouseover') + .click() + .then(() => { + cy.getByTestID('input-field') + .type(newConfDesc) + .type('{enter}') + }) + + //assert description + cy.getByTestID('resource-list--editable-description').should( + 'contain', + newConfDesc + ) + }) + it('can view setup instructions & config text', () => { cy.getByTestID('resource-card').should('have.length', 1) diff --git a/cypress/e2e/tokens.test.ts b/cypress/e2e/tokens.test.ts index 6d72c2d700..cfd3b64d02 100644 --- a/cypress/e2e/tokens.test.ts +++ b/cypress/e2e/tokens.test.ts @@ -338,42 +338,47 @@ describe('tokens', () => { .should('be.visible') }) }) + }) - it('can view a token', () => { - cy.getByTestID('token-card token test \u0950').click() - - // title match - cy.getByTestID('overlay--container').should('be.visible') - cy.getByTestID('overlay--header').should('contain', 'token test \u0950') - - // summary match - cy.getByTestID('permissions-section').should('have.length', 4) - cy.getByTestID('permissions-section') - .contains('views') - .should('be.visible') - cy.getByTestID('permissions-section') - .contains('documents') - .should('be.visible') - cy.getByTestID('permissions-section') - .contains('dashboards') - .should('be.visible') - cy.getByTestID('permissions-section') - .contains('buckets') - .should('be.visible') + it('can view a token', () => { + const tknName = 'token test \u0950' + cy.getByTestID('token-name ' + tknName).click() + // title match + cy.getByTestID('overlay--container').should('be.visible') + cy.getByTestID('overlay--header').should('contain', tknName) + cy.get('.code-snippet--label').should('contain', tknName) + + // summary match + cy.getByTestID('permissions-section').should('have.length', 4) + cy.getByTestID('permissions-section') + .contains('views') + .should('be.visible') + cy.getByTestID('permissions-section') + .contains('documents') + .should('be.visible') + cy.getByTestID('permissions-section') + .contains('dashboards') + .should('be.visible') + cy.getByTestID('permissions-section') + .contains('buckets') + .should('be.visible') + + /* // copy to clipboard + notification cy.getByTestID('button-copy').click() cy.getByTestID('notification-success').should($msg => { - expect($msg).to.contain('has been copied to clipboard') - }) - // todo check system clipboard + expect($msg).to.contain('Token has been copied to clipboard') + }) + */ - // close button - cy.getByTestID('overlay--header').within(() => { - cy.get('button').click() - }) - cy.getByTestID('overlay--container').should('not.be.visible') + // todo check system clipboard + + // close button + cy.getByTestID('overlay--header').within(() => { + cy.get('.cf-overlay--dismiss').click() }) + cy.getByTestID('overlay--container').should('not.be.visible') }) it('can edit the description', () => { @@ -461,4 +466,236 @@ describe('tokens', () => { }) }) }) + + it('can Select all buckets in Generate Read/Write token', () => { + // create some extra buckets + cy.get('@org').then(({id, name}: Organization) => { + cy.createBucket(id, name, 'Magna Carta').then(() => { + cy.createBucket(id, name, 'Sicilsky Bull').then(() => { + cy.createBucket(id, name, 'A la Carta') + }) + }) + }) + + //"Select all" button function test + // open overlay + cy.getByTestID('dropdown-button--gen-token').click() + cy.getByTestIDSubStr('dropdown-item').should('have.length', 2) + cy.getByTestID('dropdown-item generate-token--read-write').click() + cy.getByTestID('overlay--container').should('be.visible') + + //input token description + cy.getByTestID('input-field--descr') + .clear() + .type('Select all test') + .should('have.value', 'Select all test') + + //select all buckets and save + cy.getByTestID('grid--column') + .eq(0) + .within(() => { + cy.getByTestID('selector-list--header').within(() => { + cy.getByTitle('Select All').click() + }) + }) + + cy.getByTestID('grid--column') + .eq(1) + .within(() => { + cy.getByTestID('selector-list--header').within(() => { + cy.getByTitle('Select All').click() + }) + }) + + //save and assert the notification + cy.getByTestID('button--save') + .click() + .then(() => { + cy.getByTestID('notification-success--children').contains( + 'Token was created successfully' + ) + }) + + cy.getByTestID('token-card Select all test') + + //"All buckets" button function test + // open overlay + cy.getByTestID('dropdown-button--gen-token').click() + cy.getByTestID('dropdown-item generate-token--read-write').click() + cy.getByTestID('overlay--container').should('be.visible') + + //input token description + cy.getByTestID('input-field--descr') + .clear() + .type('All buckets test') + .should('have.value', 'All buckets test') + + //select all buckets with "All buckets" button and save + cy.getByTestID('select-group') + .eq(0) + .within(() => { + cy.getByTestID('select-group--option') + .contains('All Buckets') + .click() + }) + + cy.getByTestID('select-group') + .eq(1) + .within(() => { + cy.getByTestID('select-group--option') + .contains('All Buckets') + .click() + }) + + //save and assert the notification + cy.getByTestID('button--save') + .click() + .then(() => { + cy.getByTestID('notification-success--children').contains( + 'Token was created successfully' + ) + }) + + cy.getByTestID('token-name All buckets test').click() + + //assert that there is read and write permission + cy.getByTestID('permissions-section') + .eq(0) + .contains('write') + cy.getByTestID('permissions-section') + .eq(0) + .contains('read') + + cy.get('.cf-overlay--dismiss').click() + + //select all - only write permission + // open overlay + cy.getByTestID('dropdown-button--gen-token').click() + cy.getByTestIDSubStr('dropdown-item').should('have.length', 2) + cy.getByTestID('dropdown-item generate-token--read-write').click() + cy.getByTestID('overlay--container').should('be.visible') + + //input token description + cy.getByTestID('input-field--descr') + .clear() + .type('Write only test') + .should('have.value', 'Write only test') + + //select all buckets + cy.getByTestID('grid--column') + .eq(0) + .within(() => { + cy.getByTestID('selector-list--header').within(() => { + cy.getByTitle('Select All').click() + }) + }) + + cy.getByTestID('grid--column') + .eq(1) + .within(() => { + cy.getByTestID('selector-list--header').within(() => { + cy.getByTitle('Select All').click() + }) + }) + + //deselect buckets in the read column + cy.getByTestID('grid--column') + .eq(0) + .within(() => { + cy.getByTestID('selector-list--header').within(() => { + cy.getByTitle('Deselect All').click() + }) + }) + + //save and assert the notification + cy.getByTestID('button--save') + .click() + .then(() => { + cy.getByTestID('notification-success--children').contains( + 'Token was created successfully' + ) + }) + + cy.getByTestID('token-name Write only test').click() + + //assert that there is only write permission + cy.getByTestID('permissions-section') + .eq(0) + .contains('write') + .then(() => { + cy.getByTestID('permissions-section') + .eq(0) + .within(() => { + cy.getByTestID('permissions--item') + .contains('read') + .should('not.exist') + }) + }) + + cy.get('.cf-overlay--dismiss').click() + + //select all - only read permission + // open overlay + cy.getByTestID('dropdown-button--gen-token').click() + cy.getByTestIDSubStr('dropdown-item').should('have.length', 2) + cy.getByTestID('dropdown-item generate-token--read-write').click() + cy.getByTestID('overlay--container').should('be.visible') + + //input token description + cy.getByTestID('input-field--descr') + .clear() + .type('Read only test') + .should('have.value', 'Read only test') + + //select all buckets + cy.getByTestID('grid--column') + .eq(0) + .within(() => { + cy.getByTestID('selector-list--header').within(() => { + cy.getByTitle('Select All').click() + }) + }) + + cy.getByTestID('grid--column') + .eq(1) + .within(() => { + cy.getByTestID('selector-list--header').within(() => { + cy.getByTitle('Select All').click() + }) + }) + + //deselect buckets in the read column + cy.getByTestID('grid--column') + .eq(1) + .within(() => { + cy.getByTestID('selector-list--header').within(() => { + cy.getByTitle('Deselect All').click() + }) + }) + + //save and assert the notification + cy.getByTestID('button--save') + .click() + .then(() => { + cy.getByTestID('notification-success--children').contains( + 'Token was created successfully' + ) + }) + + cy.getByTestID('token-name Read only test').click() + + //assert that there is only write permission + cy.getByTestID('permissions-section') + .eq(0) + .contains('read') + .then(() => { + cy.getByTestID('permissions-section') + .eq(0) + .within(() => { + cy.getByTestID('permissions--item') + .contains('write') + .should('not.exist') + }) + }) + }) }) diff --git a/cypress/fixtures/dashboard-import.json b/cypress/fixtures/dashboard-import.json new file mode 100644 index 0000000000..163851efa7 --- /dev/null +++ b/cypress/fixtures/dashboard-import.json @@ -0,0 +1,167 @@ +{ + "meta": { + "version": "1", + "type": "dashboard", + "name": "IMPORT dashboard-Template", + "description": "template created from dashboard: IMPORT dashboard" + }, + "content": { + "data": { + "type": "dashboard", + "attributes": { + "name": "IMPORT dashboard", + "description": "Desk desc" + }, + "relationships": { + "label": { + "data": [ + { + "type": "label", + "id": "064b5eb6c11a5000" + } + ] + }, + "cell": { + "data": [ + { + "type": "cell", + "id": "064b5f0d751a5000" + }, + { + "type": "cell", + "id": "064b5f48459a5000" + } + ] + }, + "variable": { + "data": [] + } + } + }, + "included": [ + { + "type": "label", + "id": "064b5eb6c11a5000", + "attributes": { + "name": "bar", + "properties": { + "color": "#ff00ff", + "description": "test bar" + } + } + }, + { + "id": "064b5f0d751a5000", + "type": "cell", + "attributes": { + "x": 0, + "y": 0, + "w": 1, + "h": 5 + }, + "relationships": { + "view": { + "data": { + "type": "view", + "id": "064b5f0d751a5000" + } + } + } + }, + { + "id": "064b5f48459a5000", + "type": "cell", + "attributes": { + "x": 4, + "y": 0, + "w": 6, + "h": 7 + }, + "relationships": { + "view": { + "data": { + "type": "view", + "id": "064b5f48459a5000" + } + } + } + }, + { + "type": "view", + "id": "064b5f0d751a5000", + "attributes": { + "name": "cellll", + "properties": { + "shape": "chronograf-v2", + "type": "heatmap", + "queries": [ + { + "text": "", + "editMode": "advanced", + "name": "", + "builderConfig": { + "buckets": [], + "tags": [ + { + "key": "_measurement", + "values": [], + "aggregateFunctionType": "filter" + } + ], + "functions": [ + { + "name": "mean" + } + ], + "aggregateWindow": { + "period": "auto", + "fillValues": false + } + } + } + ], + "colors": [ + "#000004", + "#110a30", + "#320a5e", + "#57106e", + "#781c6d", + "#9a2865", + "#bc3754", + "#d84c3e", + "#ed6925", + "#f98e09", + "#fbb61a", + "#f4df53" + ], + "binSize": 10, + "xColumn": "", + "yColumn": "", + "xAxisLabel": "", + "yAxisLabel": "", + "xPrefix": "", + "xSuffix": "", + "yPrefix": "", + "ySuffix": "", + "note": "", + "showNoteWhenEmpty": false, + "timeFormat": "" + } + } + }, + { + "type": "view", + "id": "064b5f48459a5000", + "attributes": { + "name": "Name this Cell", + "properties": { + "shape": "chronograf-v2", + "type": "markdown", + "note": "# Note about no tea" + } + } + } + ] + }, + "labels": [] +} \ No newline at end of file diff --git a/cypress/fixtures/user.json b/cypress/fixtures/user.json new file mode 100644 index 0000000000..d83ac5e6a1 --- /dev/null +++ b/cypress/fixtures/user.json @@ -0,0 +1,6 @@ +{ + "username": "u1", + "password": "password", + "org": "deforg", + "bucket": "defbuck" +} diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index c470639f6d..ad61f5d151 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -2,20 +2,21 @@ import {NotificationEndpoint} from '../../src/types' import 'cypress-file-upload' export const signin = (): Cypress.Chainable => { - /*\ OSS login + // OSS login + return cy.fixture('user').then(({username, password}) => { return cy.setupUser().then(body => { return cy .request({ method: 'POST', url: '/api/v2/signin', - auth: {user: Cypress.env('username'), pass: Cypress.env('password')}, + auth: {user: username, pass: password}, }) .then(() => { return cy.wrap(body) }) }) - \*/ + /*\ return cy.setupUser().then(body => { return cy .visit('/api/v2/signin') @@ -24,6 +25,7 @@ export const signin = (): Cypress.Chainable => { .then(() => cy.get('#submit-login').click()) .then(() => cy.get('.theme-btn--success').click()) .then(() => cy.wrap(body)) + \*/ }) } @@ -436,10 +438,19 @@ export const createToken = ( // TODO: have to go through setup because we cannot create a user w/ a password via the user API export const setupUser = (): Cypress.Chainable => { + return cy.fixture('user').then(({username, password, org, bucket}) => { + return cy.request({ + method: 'POST', + url: '/api/v2/setup', + body: {username, password, org, bucket}, + }) + }) + /*\ return cy.request({ method: 'GET', url: '/debug/provision', }) + \*/ } export const flush = () => { @@ -447,8 +458,6 @@ export const flush = () => { method: 'GET', url: '/debug/flush', }) - - cy.wait(500) } export const lines = (numLines = 3) => { @@ -474,14 +483,12 @@ export const lines = (numLines = 3) => { export const writeData = ( lines: string[] ): Cypress.Chainable => { - return cy.request({ - method: 'POST', - url: - '/api/v2/write?org=' + - Cypress.env('org') + - '&bucket=' + - Cypress.env('bucket'), - body: lines.join('\n'), + return cy.fixture('user').then(({org, bucket}) => { + cy.request({ + method: 'POST', + url: '/api/v2/write?org=' + org + '&bucket=' + bucket, + body: lines.join('\n'), + }) }) } diff --git a/package.json b/package.json index b5279346ec..de07c5ff1f 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "src": "./src" }, "scripts": { + "script": "cd ./scripts && ts-node-script", "start": "yarn install && yarn generate && cross-env TS_NODE_PROJECT=\"webpack.tsconfig.json\" && yarn run build:vendor && yarn run start:dev", "start:cloud": "yarn install && yarn generate && cross-env TS_NODE_PROJECT=\"webpack.tsconfig.json\" && yarn run build:vendor && yarn run start:dev-cloud", "start:dev": "webpack-dev-server --config ./webpack.dev.ts --progress false", @@ -30,7 +31,7 @@ "test:watch": "jest --watch --verbose false", "test:update": "jest --updateSnapshot", "test:debug": "node --inspect-brk $(npm bin)/jest --runInBand --watch --verbose false", - "test:e2e": "CYPRESS_baseUrl=http://localhost:9999 cypress run --browser chrome --reporter junit --reporter-options 'mochaFile=junit-results/test-output-[hash].xml'", + "test:e2e": "CYPRESS_baseUrl=http://localhost:8086 cypress run --browser chrome --reporter junit --reporter-options 'mochaFile=junit-results/test-output-[hash].xml'", "test:e2e:ci": "CYPRESS_baseUrl=https://localhost:443 cypress run --parallel --browser chrome --reporter junit --reporter-options 'mochaFile=junit-results/test-output-[hash].xml'", "test:e2e:report": "junit-viewer --results=junit-results --save-file=cypress/site/junit-report.html", "test:e2e:clean": "rm junit-results/*.xml", @@ -38,14 +39,15 @@ "test:circleci": "yarn run test:ci --maxWorkers=2", "test:ci": "JEST_JUNIT_OUTPUT_DIR=\"./coverage\" jest --ci --coverage", "lint": "yarn tsc && yarn prettier && yarn eslint", - "eslint": "eslint '{src,cypress}/**/*.{ts,tsx}'", + "lint:fix": "yarn prettier:fix && yarn eslint:fix", + "eslint": "eslint '{src,cypress,scripts}/**/*.{ts,tsx}'", "eslint:circleci": "eslint", - "eslint:fix": "eslint --fix '{src,cypress}/**/*.{ts,tsx}'", - "prettier": "prettier --config .prettierrc.json --check '{src,cypress}/**/*.{ts,tsx}'", - "prettier:fix": "prettier --config .prettierrc.json --write '{src,cypress}/**/*.{ts,tsx}'", + "eslint:fix": "eslint --fix '{src,cypress,scripts}/**/*.{ts,tsx}'", + "prettier": "prettier --config .prettierrc.json --check '{src,cypress,scripts}/**/*.{ts,tsx}'", + "prettier:fix": "prettier --config .prettierrc.json --write '{src,cypress,scripts}/**/*.{ts,tsx}'", "tsc": "tsc -p ./tsconfig.json --noEmit --pretty --skipLibCheck", "tsc:cypress": "tsc -p ./cypress/tsconfig.json --noEmit --pretty --skipLibCheck", - "cy": "CYPRESS_baseUrl=http://localhost:9999 cypress open", + "cy": "CYPRESS_baseUrl=http://localhost:8086 cypress open", "cy:dev": "CYPRESS_baseUrl=http://kubernetes.docker.internal:8080 cypress open", "generate": "oats https://raw.githubusercontent.com/influxdata/influxdb/master/http/swagger.yml > ./src/client/generatedRoutes.ts" }, diff --git a/scripts/db:get.ts b/scripts/db:get.ts new file mode 100644 index 0000000000..478ebfe639 --- /dev/null +++ b/scripts/db:get.ts @@ -0,0 +1,40 @@ +import {execSync} from 'child_process' +import * as fs from 'fs' + +const tempDir = '../temp/' +const influxGit = 'https://github.com/influxdb/influxdb.git' + +// TODO: remove whne UI is removed from https://github.com/influxdb/influxdb.git master +// ui build crashes when influx nested in ui. +const fakeBuildUi = () => { + const changeFile = (file: string, fnc: (current: string) => string) => { + const fileStr = fs.readFileSync(file).toString() + const fileStrNew = fnc(fileStr) + fs.writeFileSync(file, fileStrNew) + } + + const makefile = '../temp/influxdb/Makefile' + + changeFile(makefile, x => + x + .split('\n') + .map(x => (x.startsWith('SUBDIRS') ? x.replace(/ ui /, ' ') : x)) + .join('\n') + ) + + fs.symlinkSync('../../../build', '../temp/influxdb/ui/build') +} + +if (fs.existsSync(tempDir)) { + ;(fs.rmdirSync as any)(tempDir, {recursive: true}) +} + +fs.mkdirSync(tempDir) + +process.stdout.write(`Cloning influxdb from ${influxGit}`) +execSync(`git clone ${influxGit} --depth 1`, {cwd: tempDir, stdio: 'ignore'}) + +fakeBuildUi() + +process.stdout.write(`building influxdb`) +execSync(`make`, {cwd: '../temp/influxdb', stdio: 'inherit'}) diff --git a/scripts/db:run.ts b/scripts/db:run.ts new file mode 100644 index 0000000000..b9d15f5208 --- /dev/null +++ b/scripts/db:run.ts @@ -0,0 +1,12 @@ +import {execSync} from 'child_process' +import * as path from 'path' + +const tempDir = '../temp/' +const influxdbDir = path.join(tempDir, 'influxdb') +const os = process.platform + +process.stdout.write(`Starting server...\n`) +execSync( + `./bin/${os}/influxd --assets-path=../../build --e2e-testing --store=memory --feature-flags=communityTemplates=true`, + {cwd: influxdbDir, stdio: 'inherit'} +) diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 0000000000..31bf8eed75 --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "resolveJsonModule": true, + "moduleResolution": "node", + "skipLibCheck": true, + "noEmit": true + }, + "include": [ + "./**/*.ts" + ] +} diff --git a/src/dashboards/components/dashboard_index/DashboardCard.tsx b/src/dashboards/components/dashboard_index/DashboardCard.tsx index 041c6e9f50..d282374496 100644 --- a/src/dashboards/components/dashboard_index/DashboardCard.tsx +++ b/src/dashboards/components/dashboard_index/DashboardCard.tsx @@ -98,7 +98,7 @@ class DashboardCard extends PureComponent { private get contextMenu(): JSX.Element { return ( - + { @@ -59,7 +59,7 @@ class SaveAsOverlay extends PureComponent { diff --git a/src/me/components/GettingStarted.tsx b/src/me/components/GettingStarted.tsx index 681671ce63..299ca0ce9f 100644 --- a/src/me/components/GettingStarted.tsx +++ b/src/me/components/GettingStarted.tsx @@ -73,6 +73,7 @@ const GettingStarted: FunctionComponent = ({orgID, history}) => {