From 57874f9db2a8bbbaf611a16247e84365b5eae455 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 27 May 2022 14:31:17 +0200 Subject: [PATCH] feat(exports): Dashboard / Insight exporting (#9830) * Adds chromium / selenium for image exporting * Added uploading of downloads folder to artefacts * Adds ExportButton to generate desired asset --- .github/actions/run-backend-tests/action.yml | 1 + .github/workflows/ci-async-migrations.yml | 1 + .github/workflows/ci-backend.yml | 1 + .github/workflows/docker-image-test.yml | 6 + .github/workflows/e2e.yml | 14 +- .gitignore | 2 + bin/e2e-test-runner | 5 +- cypress.e2e.json | 3 +- cypress/README.md | 24 +- .../data/exports/export-pageview-count.png | Bin 0 -> 63636 bytes cypress/fixtures/api/decide.js | 2 +- cypress/integration/exports.js | 42 +++ cypress/integration/licenses.js | 2 +- cypress/integration/trends.js | 10 +- cypress/plugins/index.js | 56 ++++ dev.Dockerfile | 7 + docker-compose.dev.yml | 1 + ee/bin/docker-ch-test | 3 +- frontend/build.mjs | 15 + .../{shared_dashboard.ejs => exporter.html} | 4 +- frontend/src/exporter/ExportViewer.scss | 10 + frontend/src/exporter/ExportViewer.tsx | 88 ++++++ .../ExportedInsight/ExportedInsight.scss | 41 +++ .../ExportedInsight/ExportedInsight.tsx | 63 +++++ frontend/src/lib/api.ts | 15 + .../components/ExportButton/ExportButton.tsx | 75 +++++ .../components/ExportButton/exporterLogic.ts | 106 +++++++ .../components/InsightCard/InsightCard.tsx | 260 ++++++++++-------- .../InsightLegend/InsightLegend.scss | 33 ++- .../InsightLegend/InsightLegend.tsx | 29 +- frontend/src/lib/constants.tsx | 1 + frontend/src/scenes/dashboard/Dashboard.tsx | 33 ++- .../src/scenes/dashboard/DashboardHeader.tsx | 10 +- .../src/scenes/dashboard/DashboardItems.tsx | 9 +- .../src/scenes/dashboard/SharedDashboard.scss | 32 +++ .../src/scenes/dashboard/SharedDashboard.tsx | 56 ++-- .../src/scenes/dashboard/dashboardLogic.ts | 2 +- .../EditorFilters/EFPathsEventTypes.tsx | 5 +- .../EditorFilters/EFRetentionSummary.tsx | 2 +- frontend/src/scenes/insights/Insight.scss | 8 - frontend/src/scenes/insights/Insight.tsx | 60 +++- .../src/scenes/insights/InsightSaveButton.tsx | 1 - .../insights/InsightTabs/RetentionTab.tsx | 2 +- frontend/src/scenes/insights/insightLogic.ts | 12 +- frontend/src/scenes/paths/Paths.scss | 15 +- ...LineGraph.scss => RetentionContainer.scss} | 6 +- .../scenes/retention/RetentionContainer.tsx | 1 + .../scenes/retention/RetentionLineGraph.tsx | 1 - .../saved-insights/savedInsightsLogic.ts | 10 +- frontend/src/styles/global.scss | 13 + frontend/src/styles/style.scss | 8 - frontend/src/types.ts | 5 +- latest_migrations.manifest | 3 +- package.json | 2 + posthog/api/__init__.py | 3 + posthog/api/decide.py | 7 +- posthog/api/exports.py | 130 +++++++++ posthog/api/test/test_exports.py | 192 +++++++++++++ posthog/api/user.py | 5 +- posthog/migrations/0238_exportedasset.py | 53 ++++ posthog/models/__init__.py | 2 + posthog/models/exported_asset.py | 56 ++++ posthog/settings/__init__.py | 2 +- posthog/tasks/exporter.py | 129 +++++++++ posthog/tasks/test/test_exporter.py | 43 +++ posthog/urls.py | 2 + posthog/utils.py | 13 +- production.Dockerfile | 6 + requirements-dev.in | 1 + requirements-dev.txt | 94 +++++-- requirements.in | 2 + requirements.txt | 44 ++- webpack.config.js | 23 +- yarn.lock | 12 + 74 files changed, 1711 insertions(+), 324 deletions(-) create mode 100644 cypress/data/exports/export-pageview-count.png create mode 100644 cypress/integration/exports.js rename frontend/src/{shared_dashboard.ejs => exporter.html} (77%) create mode 100644 frontend/src/exporter/ExportViewer.scss create mode 100644 frontend/src/exporter/ExportViewer.tsx create mode 100644 frontend/src/exporter/ExportedInsight/ExportedInsight.scss create mode 100644 frontend/src/exporter/ExportedInsight/ExportedInsight.tsx create mode 100644 frontend/src/lib/components/ExportButton/ExportButton.tsx create mode 100644 frontend/src/lib/components/ExportButton/exporterLogic.ts create mode 100644 frontend/src/scenes/dashboard/SharedDashboard.scss rename frontend/src/scenes/retention/{RetentionLineGraph.scss => RetentionContainer.scss} (78%) create mode 100644 posthog/api/exports.py create mode 100644 posthog/api/test/test_exports.py create mode 100644 posthog/migrations/0238_exportedasset.py create mode 100644 posthog/models/exported_asset.py create mode 100644 posthog/tasks/exporter.py create mode 100644 posthog/tasks/test/test_exporter.py diff --git a/.github/actions/run-backend-tests/action.yml b/.github/actions/run-backend-tests/action.yml index d2af27f17416b..1fc221184b6c2 100644 --- a/.github/actions/run-backend-tests/action.yml +++ b/.github/actions/run-backend-tests/action.yml @@ -76,6 +76,7 @@ runs: touch frontend/dist/index.html touch frontend/dist/layout.html touch frontend/dist/shared_dashboard.html + touch frontend/dist/exporter.html - name: Wait for Clickhouse & Kafka shell: bash diff --git a/.github/workflows/ci-async-migrations.yml b/.github/workflows/ci-async-migrations.yml index ab9042e499b4b..d02797c7973c8 100644 --- a/.github/workflows/ci-async-migrations.yml +++ b/.github/workflows/ci-async-migrations.yml @@ -72,6 +72,7 @@ jobs: touch frontend/dist/index.html touch frontend/dist/layout.html touch frontend/dist/shared_dashboard.html + touch frontend/dist/exporter.html - name: Wait for Clickhouse & Kafka shell: bash diff --git a/.github/workflows/ci-backend.yml b/.github/workflows/ci-backend.yml index 15ffbdcd84267..a50ab04a0e412 100644 --- a/.github/workflows/ci-backend.yml +++ b/.github/workflows/ci-backend.yml @@ -366,6 +366,7 @@ jobs: touch frontend/dist/index.html touch frontend/dist/layout.html touch frontend/dist/shared_dashboard.html + touch frontend/dist/exporter.html - name: Run cloud tests (posthog-cloud) run: | diff --git a/.github/workflows/docker-image-test.yml b/.github/workflows/docker-image-test.yml index f2ef8ed11e4b6..798c84318f9f3 100644 --- a/.github/workflows/docker-image-test.yml +++ b/.github/workflows/docker-image-test.yml @@ -24,3 +24,9 @@ jobs: with: push: false tags: posthog/posthog:testing + + - name: Output image info including size + run: | + image_size_bytes=$(docker image inspect ${{ steps.docker_build.outputs.imageid }} | jq -r '.[0].Size') + echo "### Build info" >> $GITHUB_STEP_SUMMARY + echo "Image size: $(jq -n $image_size_bytes/1000000000)GB" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 39cf06a7891cd..ef1755a092fdb 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -15,7 +15,7 @@ env: SELF_CAPTURE: 0 E2E_TESTING: 1 EMAIL_HOST: 'email.test.posthog.net' # used to test password resets - SITE_URL: 'test.posthog.net' # used to test password resets + SITE_URL: 'http://localhost:8000' # used to test password resets NO_RESTART_LOOP: 1 CLICKHOUSE_SECURE: 0 OBJECT_STORAGE_ENABLED: 1 @@ -146,20 +146,26 @@ jobs: install-command: echo "no" spec: ${{ matrix.specs }} - name: Archive test screenshots - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: screenshots path: cypress/screenshots if: ${{ failure() }} + - name: Archive test downloads + uses: actions/upload-artifact@v3 + with: + name: downloads + path: cypress/downloads + if: ${{ failure() }} - name: Archive test videos - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: videos path: cypress/videos if: ${{ failure() }} - name: Show logs on failure # use artefact here, as I think the output will be too large for display in an action - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: logs path: /tmp/logs diff --git a/.gitignore b/.gitignore index 6b33e0494340c..5bfd5c070f125 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ yarn-error.log .yalc yalc.lock cypress/screenshots/ +cypress/downloads/ +cypress/**/*.diff.png docker-compose.prod.yml .python-version *.isorted diff --git a/bin/e2e-test-runner b/bin/e2e-test-runner index eb244a5800e1b..59f755b75e96c 100755 --- a/bin/e2e-test-runner +++ b/bin/e2e-test-runner @@ -1,9 +1,8 @@ #!/bin/bash set -e - export DEBUG=1 -export NO_RESTART_LOOP=1 +export NO_RESTART_LOOP=1 export CYPRESS_BASE_URL=http://localhost:8080 export OPT_OUT_CAPTURE=1 export SECURE_COOKIES=0 @@ -15,7 +14,7 @@ export CLICKHOUSE_SECURE=0 export E2E_TESTING=1 export SECRET_KEY=e2e_test export EMAIL_HOST=email.test.posthog.net -export SITE_URL=test.posthog.net +export SITE_URL=http://localhost:8080 export REDIS_URL=redis://localhost/ DATABASE="posthog_e2e_test" export PGHOST="${PGHOST:=localhost}" diff --git a/cypress.e2e.json b/cypress.e2e.json index 37407cc09491e..3afa385f155cc 100644 --- a/cypress.e2e.json +++ b/cypress.e2e.json @@ -5,5 +5,6 @@ "requestTimeout": 8000, "pageLoadTimeout": 80000, "projectId": "twojfp", - "viewportWidth": 1200 + "viewportWidth": 1200, + "trashAssetsBeforeRuns": true } diff --git a/cypress/README.md b/cypress/README.md index dfc8d5c1bc5e9..9203f7aa7091d 100644 --- a/cypress/README.md +++ b/cypress/README.md @@ -1,5 +1,3 @@ - - ## Testing Feature Flags The Cypress tests run with a PostHog instance that has no feature flags set up. @@ -7,14 +5,14 @@ The Cypress tests run with a PostHog instance that has no feature flags set up. To test feature flags you can intercept the call to the `decide` endpoint ```javascript - // sometimes the system under test calls `/decide` - // and sometimes it calls https://app.posthog.com/decide - cy.intercept(/.*\/decide\/.*/, (req) => - req.reply( - decideResponse({ - // add feature flags here, for e.g. - // 'feature-flag-key': true, - }) - ) - ) -``` \ No newline at end of file +// sometimes the system under test calls `/decide` +// and sometimes it calls https://app.posthog.com/decide +cy.intercept('https://app.posthog.com/decide/*', (req) => + req.reply( + decideResponse({ + // add feature flags here, for e.g. + // 'feature-flag-key': true, + }) + ) +) +``` diff --git a/cypress/data/exports/export-pageview-count.png b/cypress/data/exports/export-pageview-count.png new file mode 100644 index 0000000000000000000000000000000000000000..19b8a045374313e3ccf94db3a02a32e431bb0826 GIT binary patch literal 63636 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

ac#KAB*IgI0vq2r^f46d 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 literal 0 HcmV?d00001 diff --git a/cypress/fixtures/api/decide.js b/cypress/fixtures/api/decide.js index ca06eae6f786a..5953ed5b9882a 100644 --- a/cypress/fixtures/api/decide.js +++ b/cypress/fixtures/api/decide.js @@ -1,4 +1,4 @@ -export default function decideResponse(featureFlags) { +export function decideResponse(featureFlags) { return { config: { enable_collect_everything: true, diff --git a/cypress/integration/exports.js b/cypress/integration/exports.js new file mode 100644 index 0000000000000..9e2575616f950 --- /dev/null +++ b/cypress/integration/exports.js @@ -0,0 +1,42 @@ +import { urls } from 'scenes/urls' + +import { decideResponse } from '../fixtures/api/decide' + +// NOTE: As the API data is randomly generated, we are only really testing here that the overall output is correct +// The actual graph is not under test +describe('Exporting Insights', () => { + beforeEach(() => { + cy.intercept('https://app.posthog.com/decide/*', (req) => + req.reply( + decideResponse({ + 'export-dashboard-insights': true, + }) + ) + ) + cy.visit(urls.insightNew()) + // apply filter + cy.get('[data-attr=trends-filters-add-filter-group]').click() + cy.get('[data-attr=property-select-toggle-0]').click() + cy.get('[data-attr=taxonomic-filter-searchfield]').click() + cy.get('[data-attr=expand-list-event_properties]').click() + cy.get('[data-attr=prop-filter-event_properties-1]').click({ force: true }) + cy.get('[data-attr=prop-val] input').type('not-applicable') + cy.get('[data-attr=prop-val] input').type('{enter}') + + // Save + cy.get('[data-attr="insight-save-button"]').click() + }) + + it('Export an Insight to png', () => { + cy.get('[data-attr=more-button]').click() + cy.get('[data-attr=export-button]').click() + cy.get('[data-attr=export-button-png]').click() + + const expecteFileName = 'export-pageview-count.png' + cy.task('compareToReferenceImage', { + source: expecteFileName, + reference: `../data/exports/${expecteFileName}`, + diffThreshold: 0.01, + }) + }) +}) diff --git a/cypress/integration/licenses.js b/cypress/integration/licenses.js index 83588db8d5106..b9ac995ba788a 100644 --- a/cypress/integration/licenses.js +++ b/cypress/integration/licenses.js @@ -2,7 +2,7 @@ describe('Licenses', () => { it('Licenses loaded', () => { cy.get('[data-attr=top-menu-toggle]').click() cy.get('[data-attr=top-menu-item-licenses]').click() - cy.get('[data-attr=breadcrumb-0]').should('contain', 'test.posthog.net') // Breadcrumbs work + cy.get('[data-attr=breadcrumb-0]').should('contain', Cypress.config().baseUrl.replace('http://', '')) // Breadcrumbs work cy.get('[data-attr=breadcrumb-1]').should('have.text', 'Licenses') // Breadcrumbs work cy.get('h1').should('contain', 'Licenses') cy.title().should('equal', 'Licenses • PostHog') // Page title works diff --git a/cypress/integration/trends.js b/cypress/integration/trends.js index 8645d86aa1b85..3d04a25e30105 100644 --- a/cypress/integration/trends.js +++ b/cypress/integration/trends.js @@ -52,7 +52,7 @@ describe('Trends', () => { cy.get('[data-attr=show-prop-filter-0]').click() cy.get('[data-attr=property-select-toggle-0]').click() cy.get('[data-attr="expand-list-event_properties"]').click() - cy.get('.taxonomic-list-row').first().click({ force: true }) + cy.get('[data-attr=prop-filter-event_properties-1]').click({ force: true }) cy.get('[data-attr=prop-val]').click({ force: true }) // cypress is odd and even though when a human clicks this the right dropdown opens // in the test that doesn't happen @@ -61,7 +61,6 @@ describe('Trends', () => { cy.get('.taxonomic-value-select').click() } }) - cy.get('[data-attr=prop-val-0]').click({ force: true }) cy.get('[data-attr=trend-line-graph]', { timeout: 8000 }).should('exist') }) @@ -74,7 +73,7 @@ describe('Trends', () => { cy.get('[data-attr=property-select-toggle-0]').click() cy.get('[data-attr=taxonomic-filter-searchfield]').click() cy.get('[data-attr="expand-list-event_properties"]').click() - cy.get('.taxonomic-list-row').first().click({ force: true }) + cy.get('[data-attr=prop-filter-event_properties-1]').click({ force: true }) cy.get('[data-attr=prop-val]').click({ force: true }) // cypress is odd and even though when a human clicks this the right dropdown opens // in the test that doesn't happen @@ -127,7 +126,7 @@ describe('Trends', () => { it('Apply property breakdown', () => { cy.get('[data-attr=add-breakdown-button]').click() cy.get('[data-attr="expand-list-event_properties"]').click() - cy.get('.taxonomic-list-row').first().click() + cy.get('[data-attr=prop-filter-event_properties-1]').click({ force: true }) cy.get('[data-attr=trend-line-graph]').should('exist') }) @@ -144,7 +143,7 @@ describe('Trends', () => { cy.get('[data-attr=property-select-toggle-0]').click() cy.get('[data-attr=taxonomic-filter-searchfield]').click() cy.get('[data-attr="expand-list-event_properties"]').click() - cy.get('.taxonomic-list-row').first().click({ force: true }) + cy.get('[data-attr=prop-filter-event_properties-1]').click({ force: true }) cy.get('[data-attr=prop-val]').click({ force: true }) // cypress is odd and even though when a human clicks this the right dropdown opens // in the test that doesn't happen @@ -153,7 +152,6 @@ describe('Trends', () => { cy.get('.taxonomic-value-select').click() } }) - cy.get('[data-attr=prop-val-0]').click({ force: true }) cy.get('[data-attr=insight-save-button]').click() cy.get('[data-attr=save-to-dashboard-button]').click() diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index eb95364a42ada..47bb022767981 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -1,3 +1,24 @@ +const path = require('path') +const fs = require('fs') +const pixelmatch = require('pixelmatch') +const PNG = require('pngjs').PNG + +const downloadDirectory = path.join(__dirname, '..', 'downloads') + +const checkFileDownloaded = async (filename, timeout, delayMs = 10) => { + const start = Date.now() + const fullFileName = `${downloadDirectory}/${filename}` + + while (Date.now() - start < timeout) { + await new Promise((res) => setTimeout(res, delayMs)) + + if (fs.existsSync(fullFileName)) { + return fullFileName + } + } + return +} + const webpackPreprocessor = require('@cypress/webpack-preprocessor') const { createEntry } = require('../../webpack.config') @@ -23,5 +44,40 @@ module.exports = (on, config) => { } }) + on('task', { + compareToReferenceImage({ source, reference, diffThreshold = 0.01, ms = 10000 }) { + return checkFileDownloaded(source, ms).then((fileExists) => { + if (!fileExists) { + return undefined + } + + const imgSrc = PNG.sync.read(fs.readFileSync(`${downloadDirectory}/${source}`)) + const imgRef = PNG.sync.read(fs.readFileSync(path.join(__dirname, reference))) + const { width, height } = imgSrc + const imgDiff = new PNG({ width, height }) + + const numDiffPixels = pixelmatch(imgSrc.data, imgRef.data, imgDiff.data, width, height, { + threshold: 0.1, + }) + + const imgDiffFilename = `${downloadDirectory}/${source}.diff.png` + + fs.writeFileSync(imgDiffFilename, PNG.sync.write(imgDiff)) + + const percentageDiff = numDiffPixels / (width * height) + + if (percentageDiff > diffThreshold) { + throw new Error( + `Reference image is off by ${(percentageDiff * 100).toFixed( + 2 + )}% (${numDiffPixels}) pixels. See ${imgDiffFilename} for more info` + ) + } + + return true + }) + }, + }) + return config } diff --git a/dev.Dockerfile b/dev.Dockerfile index f227d8bc6ff28..18cc51aeeb10e 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -27,6 +27,8 @@ RUN apk --update --no-cache add \ "make~=4.3" \ "nodejs-current~=16" \ "npm~=7" \ + "chromium~=93" \ + "chromium-chromedriver~=93" \ && npm install -g yarn@1 # Compile and install Python dependencies. @@ -87,4 +89,9 @@ RUN mkdir -p frontend/dist && \ # Expose container port and run entry point script EXPOSE 8000 EXPOSE 8234 + +ENV CHROME_BIN=/usr/bin/chromium-browser \ + CHROME_PATH=/usr/lib/chromium/ \ + CHROMEDRIVER_BIN=/usr/bin/chromedriver + CMD ["./bin/docker-dev"] diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 9a5a487b63eb2..59c57a5a530ec 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -83,6 +83,7 @@ services: PGHOST: db PGUSER: posthog PGPASSWORD: posthog + SITE_URL: http://web:8000 depends_on: - db - redis diff --git a/ee/bin/docker-ch-test b/ee/bin/docker-ch-test index 20eddc71d7b33..f1ffb1d45515a 100755 --- a/ee/bin/docker-ch-test +++ b/ee/bin/docker-ch-test @@ -7,4 +7,5 @@ mkdir -p frontend/dist touch frontend/dist/index.html touch frontend/dist/layout.html touch frontend/dist/shared_dashboard.html -pytest ee \ No newline at end of file +touch frontend/dist/exporter.html +pytest ee diff --git a/frontend/build.mjs b/frontend/build.mjs index 1b7d9cc7c9943..358d25cfbbc8e 100755 --- a/frontend/build.mjs +++ b/frontend/build.mjs @@ -44,6 +44,10 @@ export function writeSharedDashboardHtml(chunks = {}, entrypoints = []) { copyIndexHtml('src/shared_dashboard.html', 'dist/shared_dashboard.html', 'shared_dashboard', chunks, entrypoints) } +export function writeExporterHtml(chunks = {}, entrypoints = []) { + copyIndexHtml('src/exporter.html', 'dist/exporter.html', 'exporter', chunks, entrypoints) +} + startDevServer() copyPublicFolder() writeSourceCodeEditorTypes() @@ -68,6 +72,13 @@ buildInParallel( format: 'iife', outfile: path.resolve(__dirname, 'dist', 'shared_dashboard.js'), }, + { + name: 'Exporter', + entryPoints: ['src/exporter/ExportViewer.tsx'], + bundle: true, + format: 'iife', + outfile: path.resolve(__dirname, 'dist', 'exporter.js'), + }, { name: 'Toolbar', entryPoints: ['src/toolbar/index.tsx'], @@ -100,6 +111,10 @@ buildInParallel( writeSharedDashboardHtml(chunks, entrypoints) } + if (config.name === 'Exporter') { + writeExporterHtml(chunks, entrypoints) + } + createHashlessEntrypoints(entrypoints) buildsInProgress-- diff --git a/frontend/src/shared_dashboard.ejs b/frontend/src/exporter.html similarity index 77% rename from frontend/src/shared_dashboard.ejs rename to frontend/src/exporter.html index 10c16d4a718ea..4cc9f42dd2ddd 100644 --- a/frontend/src/shared_dashboard.ejs +++ b/frontend/src/exporter.html @@ -1,11 +1,11 @@ + {% include "head.html" %} - <%= htmlWebpackPlugin.tags.headTags %><%/* This adds the main.css file! */%>