diff --git a/.all-contributorsrc b/.all-contributorsrc index 0ef793ed77..d7b219ee17 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -343,6 +343,15 @@ "contributions": [ "code" ] + }, + { + "login": "aswathkk", + "name": "Aswath K", + "avatar_url": "https://avatars.githubusercontent.com/u/10436935?v=4", + "profile": "https://github.com/aswathkk", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.env.example b/.env.example index ef48c3bc9d..6dfdfab187 100644 --- a/.env.example +++ b/.env.example @@ -57,4 +57,9 @@ APPSMITH_MAIL_SMTP_TLS_ENABLED= #APPSMITH_SENTRY_ENVIRONMENT= # Configure cloud services -# APPSMITH_CLOUD_SERVICES_BASE_URL \ No newline at end of file +# APPSMITH_CLOUD_SERVICES_BASE_URL="https://release-cs.appsmith.com" + +# Google Recaptcha Config +APPSMITH_RECAPTCHA_SITE_KEY= +APPSMITH_RECAPTCHA_SECRET_KEY= +APPSMITH_RECAPTCHA_ENABLED= \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/----feature-request.md b/.github/ISSUE_TEMPLATE/----feature-request.md index 7f7ff5b05d..be54588e4d 100644 --- a/.github/ISSUE_TEMPLATE/----feature-request.md +++ b/.github/ISSUE_TEMPLATE/----feature-request.md @@ -2,7 +2,7 @@ name: "\U0001F6E0️ Feature request" about: Suggest an idea to improve appsmith title: "[Feature]" -labels: '' +labels: Enhancement assignees: Nikhil-Nandagopal --- diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md index c9820c7041..4694040b0e 100644 --- a/.github/ISSUE_TEMPLATE/---bug-report.md +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -4,7 +4,6 @@ about: Create a bug report to help us improve appsmith title: "[Bug]" labels: Bug, Needs Triaging assignees: Nikhil-Nandagopal - --- ## Description @@ -12,13 +11,17 @@ assignees: Nikhil-Nandagopal [What happened] ### Steps to reproduce the behaviour: + Add steps to reproduce this behaviour, include console / network logs & screenshots + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error ### Important Details - - Version: [Cloud / Self-Hosted vx.x] - - OS: [e.g.MacOSX] - - Browser [e.g. chrome, safari] + +- Version: [Cloud / Self-Hosted vx.x] +- OS: [e.g.MacOSX] +- Browser [e.g. chrome, safari] +- Environment [production, release, deploy preview] diff --git a/.github/ISSUE_TEMPLATE/---epic.md b/.github/ISSUE_TEMPLATE/---epic.md index 2a132b4026..ebd598123a 100644 --- a/.github/ISSUE_TEMPLATE/---epic.md +++ b/.github/ISSUE_TEMPLATE/---epic.md @@ -4,6 +4,7 @@ about: Describe the problem, solution and related issues title: "[Epic]" labels: Epic, Product Note assignees: Nikhil-Nandagopal + --- # Objective diff --git a/.github/config.json b/.github/config.json index 48b03bac49..cbc906c204 100644 --- a/.github/config.json +++ b/.github/config.json @@ -170,6 +170,10 @@ "color": "50ba23", "description": "" }, + "Import-Export-App": { + "name": "Import-Export-App", + "color": "50ba23" + }, "JS": { "name": "JS", "color": "ffc1c2", @@ -619,6 +623,11 @@ "type": "hasLabel", "value": true }, + { + "label": "iFrame", + "type": "hasLabel", + "value": true + }, { "label": "Widgets", "type": "hasLabel", @@ -648,6 +657,11 @@ "type": "hasLabel", "label": "UI Building", "value": true + }, + { + "type": "hasLabel", + "label": "UI Performance", + "value": true } ] }, @@ -664,6 +678,11 @@ "label": "Debugger", "value": true }, + { + "type": "hasLabel", + "label": "Import-Export-App", + "value": true + }, { "type": "hasLabel", "label": "Omnibar", diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index df2e2f8f51..162b9ead0c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,10 +3,12 @@ > Use this template to quickly create a well written pull request. Delete all quotes before creating the pull request. ## Description -> Please include a summary of the changes and which issue has been fixed. Please also include relevant motivation + +> Please include a summary of the changes and which issue has been fixed. Please also include relevant motivation > and context. List any dependencies that are required for this change. Fixes # (issue) + > if no issue exists, please create an issue and ask the maintainers about this first ## Type of change @@ -20,7 +22,7 @@ Fixes # (issue) ## How Has This Been Tested? -> Please describe the tests that you ran to verify your changes. Provide instructions, so we can reproduce. +> Please describe the tests that you ran to verify your changes. Provide instructions, so we can reproduce. > Please also list any relevant details for your test configuration. - Test A diff --git a/.github/release-drafter-template.yml b/.github/release-drafter-template.yml index ff28150475..e087624d0a 100644 --- a/.github/release-drafter-template.yml +++ b/.github/release-drafter-template.yml @@ -9,6 +9,9 @@ categories: - 'Bug' - title: 'πŸ“™ Documentation' label: 'Documentation' +# Any PR with the label 'skip-changelog' will not be inserted into the release notes +exclude-labels: + - 'skip-changelog' change-template: '- $TITLE (#$NUMBER)' change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. version-resolver: @@ -25,4 +28,4 @@ version-resolver: template: | ## What's new? - $CHANGES \ No newline at end of file + $CHANGES diff --git a/.github/workflows/build-rts.yml b/.github/workflows/build-rts.yml index 6ee9672092..9fc4aaca53 100644 --- a/.github/workflows/build-rts.yml +++ b/.github/workflows/build-rts.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: push: - branches: [release, master] + branches: [release, release-frozen, master] # Only trigger if files have changed in this specific path paths: - "app/rts/**" @@ -68,6 +68,14 @@ jobs: echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin docker push ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-rts:${{steps.vars.outputs.tag}} + # Build release-frozen Docker image and push to Docker Hub + - name: Push release-frozen image to Docker Hub + if: success() && github.ref == 'refs/heads/release-frozen' + run: | + docker build -t ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-rts:${{steps.vars.outputs.tag}} . + echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin + docker push ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-rts:${{steps.vars.outputs.tag}} + # Build master Docker image and push to Docker Hub - name: Push master image to Docker Hub with commit tag if: success() && github.ref == 'refs/heads/master' diff --git a/.github/workflows/client-test.yml b/.github/workflows/client-test.yml index e55457c5d2..d6f2b89ac4 100644 --- a/.github/workflows/client-test.yml +++ b/.github/workflows/client-test.yml @@ -13,7 +13,7 @@ on: # trigger for pushes to release and master push: - branches: [release, master] + branches: [release, release-frozen, master] paths: - "app/client/**" - "!app/client/cypress/manual_TestSuite/**" @@ -27,13 +27,11 @@ jobs: build: # If the build has been triggered manually via workflow_dispatch or via a push to protected branches # then we don't check for the PR approved state - # Only PR approvals of internally created PRs should trigger this workflow if: | - github.event_name == 'workflow_dispatch' || - github.event_name == 'push' || - (github.event_name == 'pull_request_review' && - github.event.review.state == 'approved' && - github.event.pull_request.head.repo.full_name == github.repository) + github.event_name == 'workflow_dispatch' || + github.event_name == 'push' || + (github.event_name == 'pull_request_review' && + github.event.review.state == 'approved') runs-on: ubuntu-latest defaults: run: @@ -116,6 +114,7 @@ jobs: REACT_APP_VERSION_ID=${{ steps.vars.outputs.version }} \ REACT_APP_VERSION_RELEASE_DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ') \ REACT_APP_GOOGLE_ANALYTICS_ID=${{ secrets.GOOGLE_TAG_MANAGER_ID }} \ + REACT_APP_SHOW_ONBOARDING_FORM=true \ yarn build # Upload the build artifact so that it can be used by the test & deploy job in the workflow @@ -197,7 +196,22 @@ jobs: path: app/client/build - name: Pull release server docker container and start it locally - if: github.ref == 'refs/heads/release' || github.event.pull_request.base.ref == 'release' + if: github.ref == 'refs/heads/release' || github.event.pull_request.base.ref == 'release' || github.event.pull_request.head.ref == 'release' + shell: bash + run: | + docker run -d --net=host --name appsmith-internal-server -p 8080:8080 \ + --env APPSMITH_MONGODB_URI=mongodb://localhost:27017/appsmith \ + --env APPSMITH_REDIS_URL=redis://localhost:6379 \ + --env APPSMITH_ENCRYPTION_PASSWORD=password \ + --env APPSMITH_ENCRYPTION_SALT=salt \ + --env APPSMITH_IS_SELF_HOSTED=false \ + --env APPSMITH_CLOUD_SERVICES_BASE_URL=https://release-cs.appsmith.com \ + --env APPSMITH_CLOUD_SERVICES_USERNAME= \ + --env APPSMITH_CLOUD_SERVICES_PASSWORD= \ + ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:release + + - name: Pull release-frozen server docker container and start it locally + if: github.ref == 'refs/heads/release-frozen' || github.event.pull_request.head.ref == 'release-frozen' shell: bash run: | docker run -d --net=host --name appsmith-internal-server -p 8080:8080 \ @@ -209,10 +223,10 @@ jobs: --env APPSMITH_CLOUD_SERVICES_BASE_URL= \ --env APPSMITH_CLOUD_SERVICES_USERNAME= \ --env APPSMITH_CLOUD_SERVICES_PASSWORD= \ - ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:release + ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:release-frozen - name: Pull master server docker container and start it locally - if: github.ref == 'refs/heads/master' || github.event.pull_request.base.ref == 'master' + if: github.ref == 'refs/heads/master' shell: bash run: | docker run -d --net=host --name appsmith-internal-server -p 8080:8080 \ @@ -221,7 +235,7 @@ jobs: --env APPSMITH_ENCRYPTION_PASSWORD=password \ --env APPSMITH_ENCRYPTION_SALT=salt \ --env APPSMITH_IS_SELF_HOSTED=false \ - --env APPSMITH_CLOUD_SERVICES_BASE_URL= \ + --env APPSMITH_CLOUD_SERVICES_BASE_URL=https://release-cs.appsmith.com \ --env APPSMITH_CLOUD_SERVICES_USERNAME= \ --env APPSMITH_CLOUD_SERVICES_PASSWORD= \ ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:nightly @@ -287,7 +301,7 @@ jobs: ui-test-result: needs: ui-test # Only run if the ui-test with matrices step is successful - if: success() + if: always() runs-on: ubuntu-latest defaults: run: @@ -295,6 +309,19 @@ jobs: steps: - run: echo "All ui-test matrices completed" + - name: Return status for ui-matrix + run: | + if [[ "${{ needs.ui-test.result }}" == "success" ]]; then + echo "Integration tests completed successfully!"; + exit 0; + elif [[ "${{ needs.ui-test.result }}" == "skipped" ]]; then + echo "Integration tests were skipped"; + exit 1; + else + echo "Integration tests have failed"; + exit 1; + fi + package: needs: ui-test runs-on: ubuntu-latest @@ -302,7 +329,7 @@ jobs: run: working-directory: app/client # Run this job only if all the previous steps are a success and the reference if the release or master branch - if: success() && (github.ref == 'refs/heads/release' || github.ref == 'refs/heads/master') + if: (success() && github.ref == 'refs/heads/release') || github.ref == 'refs/heads/master' steps: # Checkout the code @@ -336,6 +363,14 @@ jobs: echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin docker push ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:${{steps.branch_name.outputs.tag}} + # Build release-frozen Docker image and push to Docker Hub + - name: Push release-frozen image to Docker Hub + if: success() && github.ref == 'refs/heads/release-frozen' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + run: | + docker build -t ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:${{steps.branch_name.outputs.tag}} . + echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin + docker push ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:${{steps.branch_name.outputs.tag}} + # Build master Docker image and push to Docker Hub - name: Push production image to Docker Hub with commit tag if: success() && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') diff --git a/.github/workflows/external-client-test.yml b/.github/workflows/external-client-test.yml index f83389788e..28523fdbca 100644 --- a/.github/workflows/external-client-test.yml +++ b/.github/workflows/external-client-test.yml @@ -71,6 +71,7 @@ jobs: - name: Build and test env: APPSMITH_MONGODB_URI: "mongodb://localhost:27017/mobtools" + APPSMITH_CLOUD_SERVICES_BASE_URL: "https://release-cs.appsmith.com" APPSMITH_REDIS_URL: "redis://127.0.0.1:6379" APPSMITH_ENCRYPTION_PASSWORD: "password" APPSMITH_ENCRYPTION_SALT: "salt" @@ -199,7 +200,7 @@ jobs: if(check.length == 0) { const head_sha = pull.head.sha; const { data: completed_at } = await github.checks.create({ - owner: context.repo.owner, + owner: context.repo.owner, repo: context.repo.repo, head_sha: head_sha, name: process.env.job, @@ -294,7 +295,24 @@ jobs: path: app/client/build - name: Pull release server docker container and start it locally - if: github.ref == 'refs/heads/release' || github.event.pull_request.base.ref == 'release' + if: github.ref == 'refs/heads/release' || github.event.pull_request.base.ref == 'release'|| github.event.pull_request.head.ref == 'release' + shell: bash + run: | + echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin + + docker run -d --net=host --name appsmith-internal-server -p 8080:8080 \ + --env APPSMITH_MONGODB_URI=mongodb://localhost:27017/appsmith \ + --env APPSMITH_REDIS_URL=redis://localhost:6379 \ + --env APPSMITH_ENCRYPTION_PASSWORD=password \ + --env APPSMITH_ENCRYPTION_SALT=salt \ + --env APPSMITH_IS_SELF_HOSTED=false \ + --env APPSMITH_CLOUD_SERVICES_BASE_URL=https://release-cs.appsmith.com \ + --env APPSMITH_CLOUD_SERVICES_USERNAME= \ + --env APPSMITH_CLOUD_SERVICES_PASSWORD= \ + ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:release + + - name: Pull release-frozen server docker container and start it locally + if: github.ref == 'refs/heads/release-frozen' || github.event.pull_request.head.ref == 'release-frozen' shell: bash run: | echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin @@ -308,10 +326,10 @@ jobs: --env APPSMITH_CLOUD_SERVICES_BASE_URL= \ --env APPSMITH_CLOUD_SERVICES_USERNAME= \ --env APPSMITH_CLOUD_SERVICES_PASSWORD= \ - ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:release + ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:release-frozen - name: Pull master server docker container and start it locally - if: github.ref == 'refs/heads/master' || github.event.pull_request.base.ref == 'master' + if: github.ref == 'refs/heads/master' shell: bash run: | echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin @@ -322,7 +340,7 @@ jobs: --env APPSMITH_ENCRYPTION_PASSWORD=password \ --env APPSMITH_ENCRYPTION_SALT=salt \ --env APPSMITH_IS_SELF_HOSTED=false \ - --env APPSMITH_CLOUD_SERVICES_BASE_URL= \ + --env APPSMITH_CLOUD_SERVICES_BASE_URL=https://release-cs.appsmith.com \ --env APPSMITH_CLOUD_SERVICES_USERNAME= \ --env APPSMITH_CLOUD_SERVICES_PASSWORD= \ ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:nightly @@ -426,7 +444,7 @@ jobs: if(check.length == 0) { const head_sha = pull.head.sha; const { data: completed_at } = await github.checks.create({ - owner: context.repo.owner, + owner: context.repo.owner, repo: context.repo.repo, head_sha: head_sha, name: process.env.job, @@ -461,45 +479,9 @@ jobs: run: working-directory: app/client # Run this job only if all the previous steps are a success and the reference if the release or master branch - if: success() && (github.ref == 'refs/heads/release' || github.ref == 'refs/heads/master') + if: success() && (github.ref == 'refs/heads/release' || github.ref == 'refs/heads/release-frozen' || github.ref == 'refs/heads/master') steps: - # Check out merge commit - - name: Fork based /ok-to-test checkout - uses: actions/checkout@v2 - with: - ref: "refs/pull/${{ github.event.client_payload.pull_request.number }}/merge" - - - name: Download the react build artifact - uses: actions/download-artifact@v2 - with: - name: build - path: app/client/build - - # Here, the GITHUB_REF is of type /refs/head/. We extract branch_name from this by removing the - # first 11 characters. This can be used to build images for several branches - - name: Get the version to tag the Docker image - id: branch_name - run: echo ::set-output name=tag::$(echo ${GITHUB_REF:11}) - - # Build release Docker image and push to Docker Hub - - name: Push release image to Docker Hub - if: success() && github.ref == 'refs/heads/release' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') - run: | - docker build -t ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:${{steps.branch_name.outputs.tag}} . - echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin - docker push ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:${{steps.branch_name.outputs.tag}} - - # Build master Docker image and push to Docker Hub - - name: Push production image to Docker Hub with commit tag - if: success() && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') - run: | - docker build -t ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:${GITHUB_SHA} . - docker build -t ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:nightly . - echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin - docker push ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:${GITHUB_SHA} - docker push ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-editor:nightly - # Update check run called "package" - name: Mark package job as complete uses: actions/github-script@v1 @@ -531,7 +513,7 @@ jobs: if(check.length == 0) { const head_sha = pull.head.sha; const { data: completed_at } = await github.checks.create({ - owner: context.repo.owner, + owner: context.repo.owner, repo: context.repo.repo, head_sha: head_sha, name: process.env.job, diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index 3ac0df6d93..e4e8110d4e 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: push: - branches: [release, master] + branches: [release, release-frozen, master] # Only trigger if files have changed in this specific path paths: - "app/server/**" @@ -83,6 +83,7 @@ jobs: - name: Build and test env: APPSMITH_MONGODB_URI: "mongodb://localhost:27017/mobtools" + APPSMITH_CLOUD_SERVICES_BASE_URL: "https://release-cs.appsmith.com" APPSMITH_REDIS_URL: "redis://127.0.0.1:6379" APPSMITH_ENCRYPTION_PASSWORD: "password" APPSMITH_ENCRYPTION_SALT: "salt" @@ -102,6 +103,14 @@ jobs: echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin docker push ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:${{steps.vars.outputs.tag}} + # Build release-frozen Docker image and push to Docker Hub + - name: Push release-frozen image to Docker Hub + if: success() && github.ref == 'refs/heads/release-frozen' + run: | + docker build --build-arg APPSMITH_SEGMENT_CE_KEY=${{ secrets.APPSMITH_SEGMENT_CE_KEY }} -t ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:${{steps.vars.outputs.tag}} . + echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin + docker push ${{ secrets.DOCKER_HUB_ORGANIZATION }}/appsmith-server:${{steps.vars.outputs.tag}} + # Build master Docker image and push to Docker Hub - name: Push master image to Docker Hub with commit tag if: success() && github.ref == 'refs/heads/master' diff --git a/LICENSE b/LICENSE index b09cd7856d..572020df3c 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2021 Appsmith Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 2f0080c996..78bb17e222 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,128 @@ -

- Appsmith.com logo -
- Build and self-host all your internal apps. -
-

+Appsmith - The Frontend Tool for Any Backend -

- Try Online Sandbox -

-

- Documentation - Β· +

+ Get Started + β€’ + Docs + β€’ + Community + β€’ + Tutorials + β€’ + Events + β€’ + Youtube + β€’ Discord - Β· - Office Hours -
-
-

+ β€’ + Templates +

---- - -
-Appsmith is a JavaScript-based visual development platform to build and launch internal tools quickly. Drag-and-drop pre-built widgets, and connect them using JavaScript to create interactive pages. Connect UI to your APIs and Databases to build complex workflows in minutes.

- -**UI Components**: Table, Chart, Form, Map, Image, Video, and many more.
-**API Support**: REST APIs, OAuth 2.0, CURL
-**Database Support**: PostgreSQL, MongoDB, MySQL, Firestore, S3, Redshift, Elastic Search, DynamoDB, Redis, and MSFT SQL Server
-**Hosting**: Cloud-hosted & On-premise - -Already familiar with Appsmith? [Quickly start building on your own](#%EF%B8%8F-quickstart). - -
- -

- - - +Turn any datasource into an internal app in minutes. Appsmith lets you drag-and-drop components to build dashboards, write logic with JavaScript objects and connect to any API, database or GraphQL source. +
+
+ +MVC-Github-Readme + +

+
+ +## Quick Start + +The fastest way to start with Appsmith is the cloud-hosted version. When you're ready, you can also host it yourself. + +- Start building apps _immediately_ with [Appsmith Cloud](https://app.appsmith.com/signup/?utm_source=github&utm_medium=social&utm_content=website&utm_campaign=null&utm_term=website) +- Install locally or on a private instance with [Docker](https://docs.appsmith.com/setup/docker?utm_source=github&utm_medium=social&utm_content=appsmith_docs&utm_campaign=null&utm_term=appsmith_docs) _(Recommended)_ +- Deploy on a [Kubernetes](https://docs.appsmith.com/setup/kubernetes?utm_source=github&utm_medium=social&utm_content=appsmith_docs&utm_campaign=null&utm_term=appsmith_docs) cluster +- Deploy to [AWS with an AMI](https://docs.appsmith.com/setup/aws-ami?utm_source=github&utm_medium=social&utm_content=appsmith_docs&utm_campaign=null&utm_term=appsmith_docs) on the marketplace +- One-click deploy to [Heroku](https://docs.appsmith.com/setup/heroku?utm_source=github&utm_medium=social&utm_content=appsmith_docs&utm_campaign=null&utm_term=appsmith_docs) + +
Build an Internal Tool with Appsmith + +## Features + +

+

-## 🏭 Features +Use [ready components](https://docs.appsmith.com/widget-reference/button) to build workflows in record time. Drag common elements like tables, charts, forms and more right into your app. -- **5-minute setup**: Deploy Appsmith on your server, or use our cloud version to start building in 5 minutes. [Quick Start](#%EF%B8%8F-quickstart) -- **Frontend as a service**: Drag-and-drop from pre-built UI widgets like table, form, and image, to build sophisticated **dashboards** and **workflows, without writing HTML/CSS**. Write JavaScript anywhere to transform data, and dynamically control widget-properties. -- **Database CRUD**: Query & update your database directly by connecting it to the UI. Connect to **PostgreSQL, MongoDB, MySQL & more!** -- **Trigger APIs**: Connect to REST APIs to build custom apps. -- **Security**: DB Credentials are AES 256 encrypted and no data is stored by appsmith. Deploy behind your private VPC for additional security! -- **One-click deployment**: Managed deployment of your apps with a click of a button. -- **Access-control**: Assign users different roles & control who can edit / view your applications. -- **Supports OAuth**: Allow users to authenticate via Google Auth or GitHub Auth. +_Includes text, forms, inputs, buttons, tables, images, charts, checkboxes, switches, radio buttons, datepickers, dropdowns, filepickers, containers, maps, modals, rich text editors, tabs & video._
-## πŸ“Ί Demo +

+ +

-Unsure if Appsmith is for you? [Watch it in action here!](http://bit.ly/appsmith-demo-github) +Appsmith automagically works using any DB, Rest API or GraphQL as a datasource, with support for OAuth 2.0 and CURL. Here's a list of all our [pre-built integrations](https://docs.appsmith.com/core-concepts/connecting-to-data-sources/connecting-to-databases). -But if you’d rather check out some real applications that can be built with Appsmith, check below: +_Currently works with PostgreSQL, MongoDB, MySQL, Firestore, S3, Redshift, Elastic Search, DynamoDB, Redis, and MSFT SQL Server._
-- [Customer Support Dashboard](https://bit.ly/cs-dashboard-appsmith) -- [Job Application Tracker](https://bit.ly/3hbYtTi) +

+ +

-## πŸƒβ€β™€οΈ Quickstart +We'll run JS anywhere! Code interactions between elements, update datasources or manipulate SQL queriesβ€”[we'll parse JS anywhere](https://docs.appsmith.com/core-concepts/writing-code).
-The following steps introduce you to building a simple user-list dashboard on Appsmith. +

+ +

-1. [Sign up on Appsmith Cloud](https://app.appsmith.com/signup?utm_source=github&utm_medium=social&utm_content=website&utm_campaign=null&utm_term=website) or [Deploy Appsmith](https://docs.appsmith.com/setup). -2. Create a new app within the organization that has already been created for you. -3. Click on the `+` icon next to the `Queries` section to add a new query in the mock database - 1. Name the query `usersQuery`. - 2. Write the query `select * from users limit 5;`. - 3. Run the query. -4. Click on the `+` icon next to the `Widgets` section and drag a table onto the screen -5. Link the table data property to the `usersQuery` using JavaScript `{{usersQuery.data}}` -6. Hit the Deploy button and checkout the view mode of the app. +[Push a button](https://docs.appsmith.com/core-concepts/access-control) to deploy & share apps with your team, or privately invite your team using OAuth 2.0. Each member can be assigned a role with access to certain parts of the app.
-Congratulations πŸŽ‰ You just built your first app on Appsmith! -Connect your own data to build apps for your team. [Read more here.](https://docs.appsmith.com/core-concepts) +#### πŸ”‘ Solid 256-bit Encryption -## πŸ“š Tutorials +Appsmith apps are [secure by default](https://docs.appsmith.com/security). All connections are TLS-encrypted, and credentials are encrypted with AES-256β€”so we can't see any of your data. For added security, try our [self-hosted options](https://docs.appsmith.com/setup).

-1. [Building an Admin Panel on MongoDB using Appsmith](https://blog.appsmith.com/building-an-admin-panel-with-mongodb-using-appsmith) ([Video](https://www.youtube.com/watch?v=tisUaIgI86k)) -2. [Building a Customer Support Dashboard in Appsmith](https://www.youtube.com/watch?v=-O_6OLREEzo&t=272s) -3. [Building a Store Catalogue Management System using Appsmith and GraphQL](https://blog.appsmith.com/building-a-store-catalogue-management-system-using-appsmith-and-graphql) -4. [Running CI/CD Jobs Manually using Appsmith](https://blog.appsmith.com/how-to-run-manual-jobs-in-gitlab-cicd) ([Video](https://www.youtube.com/watch?v=CYdeJcD4I8A)) -5. [Building a Calendly Clone in Appsmith](https://blog.appsmith.com/how-to-build-a-calendly-clone-in-30-minutes) -6. [Building Internal Tools with Appsmith](https://youtu.be/eYYYfuW-kEE) `Community` -7. [Building an Issue Tracker with Appsmith](https://dev.to/pjmantoss/how-to-build-an-issue-tracker-with-appsmith-204e) `Community` +#### πŸŽ™ Transparent Support -## πŸ“• Support & Troubleshooting +Issues are inevitable. When you have one, our entire team is around to helpβ€” -If you encountered a bug or need help troubleshooting an issue, you can use one of the following channels: +- πŸ’¬ Talk to us on [Discord](https://discord.gg/rBTTVJp) +- πŸ“„ Find a solution in our [Documentation](https://docs.appsmith.com) +- ⚠️ Open an issue right here on [GitHub](https://github.com/appsmithorg/appsmith/issues/new/choose) +- πŸ‘Ύ Ask for help on our [Forum](https://community.appsmith.com) +- Join our Beta users program and give us feedback Betasmith Community. Be a part of the community that will help shape the future of Appsmith. -- Self Help: [Documentation](https://docs.appsmith.com) -- Community Support: [Discord](https://discord.gg/rBTTVJp) -- Issue & bug tracking: [GitHub Issues](https://github.com/appsmithorg/appsmith/issues/new/choose) +## Demos -## πŸ§‘β€πŸ€β€πŸ§‘ Contributing +Check out some real applications that can be built with Appsmith: -If you're interested in contributing to Appsmith: +- πŸ‘©β€πŸ’» [Customer Support Dashboard](https://app.appsmith.com/applications/5f2aeb2580ca1f6faaed4e4a/pages/5f2d61b580ca1f6faaed4e79/?utm_source=github&utm_medium=direct&utm_content=appsmith_apps&utm_campaign=null&utm_term=appsmith_apps) +- πŸ—“οΈ [Calendly in Appsmith](https://app.appsmith.com/applications/600186605b452f525458d6d2/pages/600186605b452f525458d6d4/?utm_source=github&utm_medium=direct&utm_content=appsmith_apps&utm_campaign=null&utm_term=appsmith_apps) +- πŸ’Έ [Fundraising CRM](https://app.appsmith.com/applications/6098bdc65864501cc39c3d2f/pages/6098bdc65864501cc39c3d31/?utm_source=github&utm_medium=direct&utm_content=appsmith_apps&utm_campaign=null&utm_term=appsmith_apps) +- πŸ“„ [Application Tracking System App](https://app.appsmith.com/applications/5f43bce13697fd5df1c52ce4/pages/5f43c39c3697fd5df1c52d50/?utm_source=github&utm_medium=direct&utm_content=appsmith_apps&utm_campaign=null&utm_term=appsmith_apps) -1. Start by reading our [Contribution Guide](https://github.com/appsmithorg/appsmith/blob/master/CONTRIBUTING.md). -2. Learn how to set up your local environment, in our [developer-guide](https://github.com/appsmithorg/appsmith/blob/master/contributions/CodeContributionsGuidelines.md#-setup-for-local-development). -3. Explore our list of [good first issues](https://github.com/appsmithorg/appsmith/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+First+Issue%22). +- 🌟 Made something cool? [Share it with the community](https://discord.gg/Tx395War) +

-We are committed to fostering an open and welcoming environment in the community. Please read our [Code of Conduct](CODE_OF_CONDUCT.md). +## Tutorials -## πŸ“‘ License +Here are the latest tutorials and videos for you to learn more about Appsmith: -The Appsmith platform is available under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) (Apache-2.0). +1. [Build an Investor CRM using Appsmith on Google Sheets](https://blog.appsmith.com/build-an-investor-crm-using-appsmith-on-google-sheets-1) +2. [Building an Admin Panel on MongoDB using Appsmith](https://blog.appsmith.com/building-an-admin-panel-with-mongodb-using-appsmith) ([Video](https://www.youtube.com/watch?v=tisUaIgI86k)) +3. [Building a Discount Management Dashboard With Postgres](https://blog.appsmith.com/building-a-discount-management-dashboard-with-postgres) +4. [Building a Customer Support Dashboard in Appsmith](https://www.youtube.com/watch?v=-O_6OLREEzo) +5. [Building a Store Catalogue Management System using Appsmith and GraphQL](https://blog.appsmith.com/building-a-store-catalogue-management-system-using-appsmith-and-graphql) +6. [Using the Notion API to Build a Content Management System](https://blog.appsmith.com/using-the-notion-api-to-build-a-content-management-system) +7. [How to run manual jobs in Gitlab CI/CD](https://blog.appsmith.com/how-to-run-manual-jobs-in-gitlab-cicd) ([Video](https://www.youtube.com/watch?v=CYdeJcD4I8A)) +8. [Building a Calendly Clone in Appsmith](https://blog.appsmith.com/how-to-build-a-calendly-clone-in-30-minutes) +9. [Building Internal Tools with Appsmith](https://youtu.be/eYYYfuW-kEE) `Community` +10. [Building an Issue Tracker with Appsmith](https://dev.to/pjmantoss/how-to-build-an-issue-tracker-with-appsmith-204e) `Community` -## Contributors +Visit our [blog](https://blog.appsmith.com/) to explore more. + +## Contribute + +We love our contributors! We're committed to fostering an open and welcoming environment in the community. If you'd like to contribute anything from a bug-fix to a feature update, start hereβ€” + +- πŸ“– Read our [Contribution Guide](https://github.com/appsmithorg/appsmith/blob/master/CONTRIBUTING.md) +- 🧩 [Set up your local environment](https://github.com/appsmithorg/appsmith/blob/master/contributions/CodeContributionsGuidelines.md#-setup-for-local-development) +- πŸ‘Ύ Explore some [good first issues](https://github.com/appsmithorg/appsmith/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+First+Issue%22) +- πŸ“• Read our [Code of Conduct](CODE_OF_CONDUCT.md) + +#### Top Contributors (36) @@ -161,6 +175,7 @@ The Appsmith platform is available under the [Apache License 2.0](https://www.ap
Navdeep Singh

πŸ’» +
Aswath K

πŸ’» @@ -168,3 +183,7 @@ The Appsmith platform is available under the [Apache License 2.0](https://www.ap + +## License + +Appsmith is available under the [Apache License 2.0](https://github.com/appsmithorg/appsmith/blob/release/LICENSE). Use it wisely. diff --git a/app.json b/app.json index d6bf73c26b..1a1fb89536 100644 --- a/app.json +++ b/app.json @@ -98,6 +98,21 @@ "value": "", "required": false }, + "APPSMITH_RECAPTCHA_SITE_KEY": { + "description" : "Google reCAPTCHA v3 site key, it is required if you wish to enable protection against spam/abusive users. Read more at: https://developers.google.com/recaptcha/docs/v3", + "value": "", + "required": false + }, + "APPSMITH_RECAPTCHA_SECRET_KEY": { + "description" : "Google reCAPTCHA v3 verification secret key, it is required if you wish to enable spam protection in your backend server.", + "value": "", + "required": false + }, + "APPSMITH_RECAPTCHA_ENABLED": { + "description" : "Boolean config to enable or disable Google reCAPTCHA v3 verification feature. If set to true, both site key and secret key should be provided.", + "value": "", + "required": false + }, "APPSMITH_DISABLE_TELEMETRY": { "description" : "We want to be transparent and request that you share anonymous usage data with us. This data is purely statistical in nature and helps us understand your needs & provide better support to your self-hosted instance. You can read more about what information is collected in our documentation https://docs.appsmith.com/v/v1.2.1/setup/telemetry", "value": "false" diff --git a/app/client/.eslintrc.json b/app/client/.eslintrc.json index fe0ab77466..7cf9b9c110 100644 --- a/app/client/.eslintrc.json +++ b/app/client/.eslintrc.json @@ -1,7 +1,7 @@ // This JSON file configures the eslint plugin. It supports comments as well as per the JSON5 spec { "parser": "@typescript-eslint/parser", - "plugins": ["react", "@typescript-eslint", "prettier", "react-hooks"], + "plugins": ["react", "@typescript-eslint", "prettier", "react-hooks","sort-destructure-keys"], "extends": [ "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react "plugin:@typescript-eslint/recommended", @@ -34,7 +34,8 @@ "react/self-closing-comp": "error", "react/jsx-sort-props": "error", "react/jsx-fragments": "error", - "react/jsx-no-useless-fragment": "error" + "react/jsx-no-useless-fragment": "error", + "sort-destructure-keys/sort-destructure-keys": ["error", {"caseSensitive": false}] }, "settings": { "react": { diff --git a/app/client/README.md b/app/client/README.md index 2a275ea504..4a5ba02f8e 100755 --- a/app/client/README.md +++ b/app/client/README.md @@ -6,4 +6,4 @@ This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). -For details on setting up your development machine, please refer to the [Setup Guide](https://github.com/appsmithorg/appsmith/blob/release/contributions/ClientSetup.md) +For details on setting up your development machine, please refer to the [Setup Guide](../../contributions/ClientSetup.md) diff --git a/app/client/build.sh b/app/client/build.sh index c668865fc0..2f625ec5c4 100755 --- a/app/client/build.sh +++ b/app/client/build.sh @@ -6,7 +6,7 @@ GIT_SHA=$(eval git rev-parse HEAD) echo $GIT_SHA echo "Sentry Auth Token: $SENTRY_AUTH_TOKEN" -REACT_APP_SENTRY_RELEASE=$GIT_SHA EXTEND_ESLINT=true craco --max-old-space-size=4096 build --config craco.build.config.js +REACT_APP_SENTRY_RELEASE=$GIT_SHA REACT_APP_CLIENT_LOG_LEVEL=ERROR EXTEND_ESLINT=true craco --max-old-space-size=4096 build --config craco.build.config.js rm ./build/static/js/*.js.map echo "build finished" diff --git a/app/client/cypress.json b/app/client/cypress.json index 92d8371d5f..6ecfd0080f 100644 --- a/app/client/cypress.json +++ b/app/client/cypress.json @@ -4,6 +4,7 @@ "requestTimeout": 21000, "pageLoadTimeout": 20000, "video": true, + "videoUploadOnPasses": false, "reporter": "mochawesome", "reporterOptions": { "reportDir": "results", @@ -18,4 +19,4 @@ "runMode": 2, "openMode": 0 } -} \ No newline at end of file +} diff --git a/app/client/cypress/fixtures/CanvasResizeDsl.json b/app/client/cypress/fixtures/CanvasResizeDsl.json index 0ade4a1ac2..f26b52f98f 100644 --- a/app/client/cypress/fixtures/CanvasResizeDsl.json +++ b/app/client/cypress/fixtures/CanvasResizeDsl.json @@ -7,7 +7,7 @@ "detachFromLayout": true, "widgetId": "0", "topRow": 0, - "bottomRow": 2960, + "bottomRow": 740, "containerStyle": "none", "snapRows": 33, "parentRowSpace": 1, diff --git a/app/client/cypress/fixtures/DividerDsl.json b/app/client/cypress/fixtures/DividerDsl.json new file mode 100644 index 0000000000..36c0065140 --- /dev/null +++ b/app/client/cypress/fixtures/DividerDsl.json @@ -0,0 +1,48 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1280, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1300, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 23, + "minHeight": 1310, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "widgetName": "Divider1", + "rightColumn": 23, + "widgetId": "i5kplngd19", + "topRow": 25, + "bottomRow": 75, + "parentRowSpace": 10, + "isVisible": true, + "type": "DIVIDER_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "parentColumnSpace": 19.75, + "dynamicTriggerPathList": [], + "leftColumn": 15, + "dynamicBindingPathList": [], + "orientation": "horizontal", + "capType": "nc", + "capSide": -1, + "strokeStyle": "solid", + "dividerColor": "black", + "thickness": 2 + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/Mapdsl.json b/app/client/cypress/fixtures/Mapdsl.json index 490ab5f87b..2f75e8c860 100644 --- a/app/client/cypress/fixtures/Mapdsl.json +++ b/app/client/cypress/fixtures/Mapdsl.json @@ -144,7 +144,7 @@ "parentRowSpace": 38, "leftColumn": 3, "rightColumn": 11, - "topRow": 0, + "topRow": 1, "bottomRow": 12, "parentId": "yt4ouwn0sk", "widgetId": "673etzjyv9", diff --git a/app/client/cypress/fixtures/application-file.json b/app/client/cypress/fixtures/application-file.json new file mode 100644 index 0000000000..5a1193ac1d --- /dev/null +++ b/app/client/cypress/fixtures/application-file.json @@ -0,0 +1,174 @@ +{ + "exportedApplication": { + "userPermissions": [ + "canComment:applications", + "manage:applications", + "read:applications", + "publish:applications", + "makePublic:applications" + ], + "name": "testing app - pk", + "isPublic": false, + "appIsExample": false, + "color": "#FE9F44", + "icon": "heart", + "new": true + }, + "datasourceList": [], + "pageList": [ + { + "userPermissions": [ + "read:pages", + "manage:pages" + ], + "unpublishedPage": { + "name": "Page1", + "layouts": [ + { + "id": "60a77186cdbfc9440388285c", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1118, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1280, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 18, + "minHeight": 1292, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "widgetName": "Button1", + "rightColumn": 4, + "isDefaultClickDisabled": true, + "widgetId": "g7jf8v3wkq", + "buttonStyle": "PRIMARY_BUTTON", + "topRow": 0, + "bottomRow": 1, + "parentRowSpace": 40, + "isVisible": true, + "type": "BUTTON_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "parentColumnSpace": 67.375, + "leftColumn": 2, + "text": "Submit", + "isDisabled": false + }, + { + "widgetName": "Chart1", + "rightColumn": 8, + "allowHorizontalScroll": false, + "widgetId": "ow55pc4z0z", + "topRow": 5, + "bottomRow": 13, + "parentRowSpace": 40, + "isVisible": true, + "type": "CHART_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "chartData": { + "pftw37090s": { + "seriesName": "Sales", + "data": [ + { + "x": "Mon", + "y": 10000 + }, + { + "x": "Tue", + "y": 12000 + }, + { + "x": "Wed", + "y": 32000 + }, + { + "x": "Thu", + "y": 28000 + }, + { + "x": "Fri", + "y": 14000 + }, + { + "x": "Sat", + "y": 19000 + }, + { + "x": "Sun", + "y": 36000 + } + ] + } + }, + "yAxisName": "Total Order Revenue $", + "parentColumnSpace": 67.375, + "chartName": "Last week's revenue", + "leftColumn": 2, + "xAxisName": "Last Week", + "chartType": "LINE_CHART" + } + ] + }, + "layoutOnLoadActions": [], + "new": false + } + ], + "userPermissions": [] + }, + "publishedPage": { + "name": "Page1", + "layouts": [ + { + "id": "60a77186cdbfc9440388285c", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1224, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1254, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 4, + "minHeight": 1292, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [] + }, + "new": false + } + ], + "userPermissions": [] + }, + "new": true + } + ], + "publishedDefaultPageName": "Page1", + "unpublishedDefaultPageName": "Page1", + "actionList": [], + "decryptedFields": {}, + "publishedLayoutmongoEscapedWidgets": {}, + "unpublishedLayoutmongoEscapedWidgets": {} +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/basicDsl.json b/app/client/cypress/fixtures/basicDsl.json new file mode 100644 index 0000000000..a3932623ec --- /dev/null +++ b/app/client/cypress/fixtures/basicDsl.json @@ -0,0 +1 @@ +{"dsl":{"widgetName":"MainContainer","backgroundColor":"none","rightColumn":966,"snapColumns":64,"detachFromLayout":true,"widgetId":"0","topRow":0,"bottomRow":320,"containerStyle":"none","snapRows":125,"parentRowSpace":1,"type":"CANVAS_WIDGET","canExtend":true,"version":23,"minHeight":1292,"parentColumnSpace":1,"dynamicBindingPathList":[],"leftColumn":0,"children":[{"isVisible":true,"inputType":"TEXT","label":"","widgetName":"Input1","version":1,"resetOnSubmit":true,"isRequired":false,"isDisabled":false,"type":"INPUT_WIDGET","isLoading":false,"parentColumnSpace":14.84375,"parentRowSpace":10,"leftColumn":23,"rightColumn":43,"topRow":8,"bottomRow":12,"parentId":"0","widgetId":"ihviqc47ev"}],"dynamicTriggerPathList":[]}} \ No newline at end of file diff --git a/app/client/cypress/fixtures/chartUpdatedDsl.json b/app/client/cypress/fixtures/chartUpdatedDsl.json new file mode 100644 index 0000000000..20a57d6a5c --- /dev/null +++ b/app/client/cypress/fixtures/chartUpdatedDsl.json @@ -0,0 +1,184 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 966, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 5168, + "containerStyle": "none", + "snapRows": 129, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 20, + "minHeight": 450, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "backgroundColor": "#FFFFFF", + "widgetName": "Container1", + "rightColumn": 32, + "orientation": "VERTICAL", + "snapColumns": 16, + "widgetId": "mxbaasg65u", + "containerStyle": "card", + "topRow": 0, + "bottomRow": 36, + "parentRowSpace": 38, + "isVisible": true, + "type": "CONTAINER_WIDGET", + "version": 1, + "isLoading": false, + "parentColumnSpace": 75.25, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "backgroundColor": "transparent", + "widgetName": "59gdivzv7s", + "rightColumn": 2408, + "orientation": "VERTICAL", + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "bxekwxgc1i", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 20, + "parentRowSpace": 1, + "isVisible": true, + "type": "CANVAS_WIDGET", + "canExtend": false, + "version": 1, + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [], + "children": [] + } + ] + }, + { + "backgroundColor": "#FFFFFF", + "widgetName": "Container3", + "rightColumn": 64, + "orientation": "VERTICAL", + "snapColumns": 16, + "widgetId": "i331vll2mg", + "containerStyle": "card", + "topRow": 36, + "bottomRow": 92, + "parentRowSpace": 38, + "isVisible": true, + "type": "CONTAINER_WIDGET", + "version": 1, + "isLoading": false, + "parentColumnSpace": 75.25, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "backgroundColor": "transparent", + "widgetName": "rhfg2vf1n5", + "rightColumn": 4816, + "orientation": "VERTICAL", + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "rglduihhzk", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 20, + "parentRowSpace": 1, + "isVisible": true, + "type": "CANVAS_WIDGET", + "canExtend": false, + "version": 1, + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [], + "children": [ + { + "isVisible": true, + "widgetName": "Chart1", + "chartType": "LINE_CHART", + "chartName": "Last week's revenue", + "allowHorizontalScroll": false, + "version": 1, + "chartData": { + "q4pm3w97mo": { + "seriesName": "Sales", + "data": "" + } + }, + "xAxisName": "Last Week", + "yAxisName": "Total Order Revenue $", + "type": "CHART_WIDGET", + "isLoading": false, + "parentColumnSpace": 14.59375, + "parentRowSpace": 10, + "leftColumn": 20, + "rightColumn": 44, + "topRow": 16, + "bottomRow": 48, + "parentId": "rglduihhzk", + "widgetId": "snzfh3qjo8", + "dynamicBindingPathList": [], + "dynamicTriggerPathList": [] + } + ] + } + ] + }, + { + "backgroundColor": "#FFFFFF", + "widgetName": "Container4", + "rightColumn": 64, + "orientation": "VERTICAL", + "snapColumns": 16, + "widgetId": "qznzsquf70", + "containerStyle": "card", + "topRow": 0, + "bottomRow": 36, + "parentRowSpace": 38, + "isVisible": true, + "type": "CONTAINER_WIDGET", + "version": 1, + "isLoading": false, + "parentColumnSpace": 75.25, + "dynamicBindingPathList": [], + "leftColumn": 32, + "children": [ + { + "backgroundColor": "transparent", + "widgetName": "3bn6uv0vy4", + "rightColumn": 2408, + "orientation": "VERTICAL", + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "7vm5mdu8ey", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 1368, + "parentRowSpace": 1, + "isVisible": true, + "type": "CANVAS_WIDGET", + "canExtend": false, + "version": 1, + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [], + "children": [ + null + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/debuggerDependencyDsl.json b/app/client/cypress/fixtures/debuggerDependencyDsl.json new file mode 100644 index 0000000000..d023180e24 --- /dev/null +++ b/app/client/cypress/fixtures/debuggerDependencyDsl.json @@ -0,0 +1,58 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1224, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1280, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 9, + "minHeight": 1292, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "isVisible": true, + "text": "Submit", + "buttonStyle": "PRIMARY_BUTTON", + "widgetName": "Button1", + "isDisabled": false, + "isDefaultClickDisabled": true, + "type": "BUTTON_WIDGET", + "isLoading": false, + "parentColumnSpace": 74, + "parentRowSpace": 40, + "leftColumn": 5, + "rightColumn": 7, + "topRow": 2, + "bottomRow": 3, + "parentId": "0", + "widgetId": "3qg87le9t4" + }, + { + "isVisible": true, + "inputType": "TEXT", + "label": "", + "widgetName": "Input1", + "type": "INPUT_WIDGET", + "isLoading": false, + "parentColumnSpace": 74, + "parentRowSpace": 40, + "leftColumn": 2, + "rightColumn": 7, + "topRow": 0, + "bottomRow": 1, + "parentId": "0", + "widgetId": "2lhsjdd5sg" + } + ] + } + } \ No newline at end of file diff --git a/app/client/cypress/fixtures/debuggerTableDsl.json b/app/client/cypress/fixtures/debuggerTableDsl.json new file mode 100644 index 0000000000..afaed3b2cf --- /dev/null +++ b/app/client/cypress/fixtures/debuggerTableDsl.json @@ -0,0 +1,42 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1224, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1280, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 9, + "minHeight": 1292, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "isVisible": true, + "label": "Data", + "widgetName": "Table1", + "searchKey": "", + "tableData": "{{TestApi.data.users}}", + "type": "TABLE_WIDGET", + "isLoading": false, + "parentColumnSpace": 74, + "parentRowSpace": 40, + "leftColumn": 1, + "rightColumn": 9, + "topRow": 7, + "bottomRow": 14, + "parentId": "0", + "widgetId": "7miqot30xy", + "dynamicBindingPathList": [] + } + ] + } + } \ No newline at end of file diff --git a/app/client/cypress/fixtures/displayWidgetDsl.json b/app/client/cypress/fixtures/displayWidgetDsl.json index 01da343433..e2fbfa257b 100644 --- a/app/client/cypress/fixtures/displayWidgetDsl.json +++ b/app/client/cypress/fixtures/displayWidgetDsl.json @@ -14,7 +14,6 @@ "type": "CANVAS_WIDGET", "canExtend": true, "dynamicBindingPathList": [], - "version": 4, "minHeight": 1292, "parentColumnSpace": 1, "leftColumn": 0, diff --git a/app/client/cypress/fixtures/example.json b/app/client/cypress/fixtures/example.json index 26d6dd6c75..053ecc28a5 100644 --- a/app/client/cypress/fixtures/example.json +++ b/app/client/cypress/fixtures/example.json @@ -201,50 +201,60 @@ "orderAmount": 9.99 } ], - "ChartCustomConfig": {"type": "area2d", - "dataSource": { - "chart": { - "caption": "Countries With Most Oil Reserves [2017-18]", - "subCaption": "In MMbbl = One Million barrels", - "xAxisName": "Country", - "yAxisName": "Reserves (MMbbl)", - "numberSuffix": "K" - }, - "data": [ - { - "label": "Venezuela", - "value": "290" - }, - { - "label": "Saudi", - "value": "260" - }, - { - "label": "Canada", - "value": "180" - }, - { - "label": "Iran", - "value": "140" - }, - { - "label": "Russia", - "value": "115" - }, - { - "label": "UAE", - "value": "100" - }, - { - "label": "US", - "value": "30" - }, - { - "label": "China", - "value": "30" - } - ] - }}, + "ChartCustomConfig": { + "type":"column2d", + "dataSource":{ + "chart":{ + "caption":"Monthly revenue for last year", + "subCaption":"Harry's SuperMart", + "xAxisName":"Month", + "yAxisName":"Revenues (In USD)", + "numberPrefix":"$", + "theme":"fusion" + }, + "data":[ + { + "label":"Jan", + "value":"420000" + }, + { + "label":"Feb", + "value":"810000" + }, + { + "label":"Mar", + "value":"720000" + }, + { + "label":"Apr", + "value":"550000" + }, + { + "label":"May", + "value":"910000" + }, + { + "label":"Jun", + "value":"256000" + }, + { + "label":"Jul", + "value":"512000" + } + ], + "trendlines":[ + { + "line":[ + { + "startvalue":"700000", + "valueOnRight":"1", + "displayvalue":"Monthly Target" + } + ] + } + ] + } + }, "TableInputWithNull": [ { "id": 2381224, diff --git a/app/client/cypress/fixtures/formInputTableDsl.json b/app/client/cypress/fixtures/formInputTableDsl.json index 1c731ed338..71eba21877 100644 --- a/app/client/cypress/fixtures/formInputTableDsl.json +++ b/app/client/cypress/fixtures/formInputTableDsl.json @@ -110,7 +110,9 @@ "topRow": 3, "bottomRow": 4, "parentId": "4lvpg75cy5", - "widgetId": "vwe9oyg9x1" + "widgetId": "vwe9oyg9x1", + "defaultText": "{{Input1.text}}", + "dynamicBindingPathList": [{"key": "defaultText"}] } ], "blueprint": { diff --git a/app/client/cypress/fixtures/formdsl.json b/app/client/cypress/fixtures/formdsl.json index 866f2a0255..f8859c48a5 100644 --- a/app/client/cypress/fixtures/formdsl.json +++ b/app/client/cypress/fixtures/formdsl.json @@ -199,7 +199,7 @@ "backgroundColor": "Gray", "rightColumn": 11, "widgetId": "z62mnh15y5", - "topRow": 0, + "topRow": 1, "bottomRow": 13, "parentRowSpace": 38, "isVisible": true, diff --git a/app/client/cypress/fixtures/listdsl.json b/app/client/cypress/fixtures/listdsl.json index 25c0fb517a..e0aab6c6ec 100644 --- a/app/client/cypress/fixtures/listdsl.json +++ b/app/client/cypress/fixtures/listdsl.json @@ -13,7 +13,7 @@ "parentRowSpace": 1, "type": "CANVAS_WIDGET", "canExtend": true, - "version": 9, + "version": 23, "minHeight": 1292, "parentColumnSpace": 1, "dynamicBindingPathList": [], @@ -25,7 +25,7 @@ "backgroundColor": "", "gridType": "vertical", "gridGap": 0, - "items": "[\n {\n \"id\": 1,\n \"email\": \"michael.lawson@reqres.in\",\n \"first_name\": \"Michael\",\n \"last_name\": \"Lawson\",\n \"avatar\": \"https://reqres.in/img/faces/7-image.jpg\"\n },\n {\n \"id\": 2,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"first_name\": \"Lindsay\",\n \"last_name\": \"Ferguson\",\n \"avatar\": \"https://reqres.in/img/faces/8-image.jpg\"\n },\n {\n \"id\": 3,\n \"email\": \"brock.lesnar@reqres.in\",\n \"first_name\": \"Brock\",\n \"last_name\": \"Lesnar\",\n \"avatar\": \"https://reqres.in/img/faces/8-image.jpg\"\n }\n]", + "listData": "[\n {\n \"id\": 1,\n \"email\": \"michael.lawson@reqres.in\",\n \"first_name\": \"Michael\",\n \"last_name\": \"Lawson\",\n \"avatar\": \"https://reqres.in/img/faces/7-image.jpg\"\n },\n {\n \"id\": 2,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"first_name\": \"Lindsay\",\n \"last_name\": \"Ferguson\",\n \"avatar\": \"https://reqres.in/img/faces/8-image.jpg\"\n },\n {\n \"id\": 3,\n \"email\": \"brock.lesnar@reqres.in\",\n \"first_name\": \"Brock\",\n \"last_name\": \"Lesnar\",\n \"avatar\": \"https://reqres.in/img/faces/8-image.jpg\"\n }\n]", "widgetName": "List1", "children": [ { @@ -60,9 +60,9 @@ "parentColumnSpace": 32, "parentRowSpace": 40, "leftColumn": 2, - "rightColumn": 6, + "rightColumn": 24, "topRow": 0, - "bottomRow": 1, + "bottomRow": 16, "parentId": "dinv2tsatk", "widgetId": "k6ct7dxg4w" }, @@ -73,15 +73,15 @@ "widgetName":"Button1", "isDisabled":false, "isDefaultClickDisabled":true, - "version":1, + "version":26, "type":"BUTTON_WIDGET", "isLoading":false, "parentColumnSpace":29.25, "parentRowSpace":40, - "leftColumn":6, - "rightColumn":8, + "leftColumn":24, + "rightColumn": 32, "topRow":1, - "bottomRow":2, + "bottomRow":8, "parentId":"dinv2tsatk", "widgetId":"fuw9p7cuek" } @@ -105,9 +105,9 @@ "type": "CONTAINER_WIDGET", "isLoading": false, "leftColumn": 0, - "rightColumn": 16, + "rightColumn": 64, "topRow": 0, - "bottomRow": 4, + "bottomRow": 16, "parentId": "0pvmmqr77m", "widgetId": "4ruj7xl5ri" } @@ -130,9 +130,9 @@ "parentColumnSpace": 74, "parentRowSpace": 40, "leftColumn": 0, - "rightColumn": 8, + "rightColumn": 32, "topRow": 0, - "bottomRow": 10, + "bottomRow": 40, "parentId": "0", "widgetId": "5bwz8xcvhj", "dynamicBindingPathList": [], diff --git a/app/client/cypress/fixtures/listwidgetdsl.json b/app/client/cypress/fixtures/listwidgetdsl.json new file mode 100644 index 0000000000..5415a2211c --- /dev/null +++ b/app/client/cypress/fixtures/listwidgetdsl.json @@ -0,0 +1,275 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 966, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 800, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 18, + "minHeight": 240, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "isVisible": true, + "backgroundColor": "", + "itemBackgroundColor": "white", + "gridType": "vertical", + "enhancements": true, + "gridGap": 0, + "listData": "", + "widgetName": "List1", + "children": [ + { + "isVisible": true, + "widgetName": "Canvas1", + "version": 1, + "containerStyle": "none", + "canExtend": false, + "detachFromLayout": true, + "dropDisabled": true, + "noPad": true, + "children": [ + { + "isVisible": true, + "backgroundColor": "white", + "widgetName": "Container1", + "containerStyle": "card", + "children": [ + { + "isVisible": true, + "widgetName": "Canvas2", + "version": 1, + "containerStyle": "none", + "canExtend": false, + "detachFromLayout": true, + "children": [ + { + "isVisible": true, + "defaultImage": "https://res.cloudinary.com/drako999/image/upload/v1589196259/default.png", + "imageShape": "RECTANGLE", + "maxZoomLevel": 1, + "image": "{{currentItem.img}}", + "widgetName": "Image1", + "version": 1, + "dynamicBindingPathList": [ + { + "key": "image" + } + ], + "dynamicTriggerPathList": [], + "type": "IMAGE_WIDGET", + "isLoading": false, + "leftColumn": 0, + "rightColumn": 4, + "topRow": 0, + "bottomRow": 3, + "parentId": "muh6tmsm1f", + "widgetId": "vr29m4code" + }, + { + "isVisible": true, + "text": "{{currentItem.name}}", + "fontSize": "PARAGRAPH", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "widgetName": "Text1", + "version": 1, + "textStyle": "HEADING", + "dynamicBindingPathList": [ + { + "key": "text" + } + ], + "dynamicTriggerPathList": [], + "type": "TEXT_WIDGET", + "isLoading": false, + "leftColumn": 4, + "rightColumn": 10, + "topRow": 0, + "bottomRow": 1, + "parentId": "muh6tmsm1f", + "widgetId": "envgv9f2j9" + }, + { + "isVisible": true, + "text": "{{currentItem.num}}", + "fontSize": "PARAGRAPH", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "widgetName": "Text2", + "version": 1, + "textStyle": "BODY", + "dynamicBindingPathList": [ + { + "key": "text" + } + ], + "dynamicTriggerPathList": [], + "type": "TEXT_WIDGET", + "isLoading": false, + "leftColumn": 4, + "rightColumn": 10, + "topRow": 1, + "bottomRow": 2, + "parentId": "muh6tmsm1f", + "widgetId": "37xbmi0bbz" + } + ], + "minHeight": null, + "type": "CANVAS_WIDGET", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": null, + "topRow": 0, + "bottomRow": null, + "parentId": "5q9jzp3d17", + "widgetId": "muh6tmsm1f" + } + ], + "version": 1, + "dragDisabled": true, + "isDeletable": false, + "disallowCopy": true, + "disablePropertyPane": true, + "type": "CONTAINER_WIDGET", + "isLoading": false, + "leftColumn": 0, + "rightColumn": 16, + "topRow": 0, + "bottomRow": 4, + "parentId": "qt3cziyljx", + "widgetId": "5q9jzp3d17" + } + ], + "minHeight": 400, + "type": "CANVAS_WIDGET", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 463, + "topRow": 0, + "bottomRow": 400, + "parentId": "wmuvmnfqm0", + "widgetId": "qt3cziyljx" + } + ], + "type": "LIST_WIDGET", + "isLoading": false, + "parentColumnSpace": 57.875, + "parentRowSpace": 40, + "leftColumn": 4, + "rightColumn": 12, + "topRow": 8, + "bottomRow": 18, + "parentId": "0", + "widgetId": "wmuvmnfqm0", + "dynamicBindingPathList": [ + { + "key": "template.Image1.image" + }, + { + "key": "template.Text1.text" + }, + { + "key": "template.Text2.text" + } + ], + "template": { + "Image1": { + "isVisible": true, + "defaultImage": "https://res.cloudinary.com/drako999/image/upload/v1589196259/default.png", + "imageShape": "RECTANGLE", + "maxZoomLevel": 1, + "image": "{{List1.listData.map((currentItem) => currentItem.img)}}", + "widgetName": "Image1", + "version": 1, + "dynamicBindingPathList": [ + { + "key": "image" + } + ], + "dynamicTriggerPathList": [], + "type": "IMAGE_WIDGET", + "isLoading": false, + "leftColumn": 0, + "rightColumn": 4, + "topRow": 0, + "bottomRow": 3, + "parentId": "muh6tmsm1f", + "widgetId": "vr29m4code" + }, + "Text1": { + "isVisible": true, + "text": "{{List1.listData.map((currentItem) => currentItem.name)}}", + "fontSize": "PARAGRAPH", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "widgetName": "Text1", + "version": 1, + "textStyle": "HEADING", + "dynamicBindingPathList": [ + { + "key": "text" + } + ], + "dynamicTriggerPathList": [], + "type": "TEXT_WIDGET", + "isLoading": false, + "leftColumn": 4, + "rightColumn": 10, + "topRow": 0, + "bottomRow": 1, + "parentId": "muh6tmsm1f", + "widgetId": "envgv9f2j9" + }, + "Text2": { + "isVisible": true, + "text": "{{List1.listData.map((currentItem) => currentItem.num)}}", + "fontSize": "PARAGRAPH", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "widgetName": "Text2", + "version": 1, + "textStyle": "BODY", + "dynamicBindingPathList": [ + { + "key": "text" + } + ], + "dynamicTriggerPathList": [], + "type": "TEXT_WIDGET", + "isLoading": false, + "leftColumn": 4, + "rightColumn": 10, + "topRow": 1, + "bottomRow": 2, + "parentId": "muh6tmsm1f", + "widgetId": "37xbmi0bbz" + } + }, + "childAutoComplete": { + "currentItem": {} + }, + "dynamicTriggerPathList": [] + } + ] + } +} diff --git a/app/client/cypress/fixtures/tableResizedColumnsDsl.json b/app/client/cypress/fixtures/tableResizedColumnsDsl.json new file mode 100644 index 0000000000..85ea1b6a2a --- /dev/null +++ b/app/client/cypress/fixtures/tableResizedColumnsDsl.json @@ -0,0 +1,505 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1280, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1310, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 23, + "minHeight": 1320, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "backgroundColor": "#ffffff", + "widgetName": "Container1", + "rightColumn": 64, + "widgetId": "sc2tsag5jw", + "containerStyle": "card", + "topRow": 0, + "bottomRow": 6, + "parentRowSpace": 10, + "isVisible": true, + "type": "CONTAINER_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "parentColumnSpace": 16.432031249999998, + "dynamicTriggerPathList": [], + "leftColumn": 0, + "dynamicBindingPathList": [], + "children": [ + { + "widgetName": "Canvas1", + "rightColumn": 525.8249999999999, + "detachFromLayout": true, + "widgetId": "v615d41hf1", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 400, + "parentRowSpace": 1, + "isVisible": true, + "canExtend": false, + "type": "CANVAS_WIDGET", + "version": 1, + "parentId": "sc2tsag5jw", + "minHeight": 400, + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "children": [] + } + ] + }, + { + "image": "https://source.unsplash.com/mGFHA_0TWnA/2048x620", + "widgetName": "Image1", + "rightColumn": 64, + "widgetId": "ah4cbb9o2e", + "topRow": 6, + "bottomRow": 39, + "parentRowSpace": 10, + "isVisible": true, + "type": "IMAGE_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "maxZoomLevel": 1, + "parentColumnSpace": 16.432031249999998, + "dynamicTriggerPathList": [], + "imageShape": "RECTANGLE", + "leftColumn": 0, + "dynamicBindingPathList": [], + "defaultImage": "https://res.cloudinary.com/drako999/image/upload/v1589196259/default.png" + }, + { + "backgroundColor": "#FFFFFF", + "widgetName": "Container2", + "rightColumn": 64, + "widgetId": "0oh55q30ji", + "containerStyle": "card", + "topRow": 39, + "bottomRow": 85, + "parentRowSpace": 10, + "isVisible": true, + "type": "CONTAINER_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "shouldScrollContents": false, + "parentColumnSpace": 16.432031249999998, + "dynamicTriggerPathList": [], + "leftColumn": 0, + "dynamicBindingPathList": [], + "children": [ + { + "widgetName": "Canvas2", + "rightColumn": 525.8249999999999, + "detachFromLayout": true, + "widgetId": "9kax26wjp6", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 460, + "parentRowSpace": 1, + "isVisible": true, + "canExtend": false, + "type": "CANVAS_WIDGET", + "version": 1, + "parentId": "0oh55q30ji", + "minHeight": 400, + "isLoading": false, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "leftColumn": 0, + "dynamicBindingPathList": [], + "children": [ + { + "widgetName": "Table2", + "columnOrder": [ + "data", + "customColumn3", + "customColumn4", + "customColumn5", + "customColumn6", + "page_id", + "customColumn2", + "customColumn7", + "customColumn8" + ], + "dynamicPropertyPathList": [], + "topRow": 4, + "bottomRow": 44, + "parentRowSpace": 10, + "type": "TABLE_WIDGET", + "parentColumnSpace": 16.182031249999998, + "dynamicTriggerPathList": [ + { + "key": "primaryColumns.customColumn2.onClick" + } + ], + "dynamicBindingPathList": [ + { + "key": "tableData" + }, + { + "key": "primaryColumns.data.computedValue" + }, + { + "key": "primaryColumns.page_id.computedValue" + }, + { + "key": "primaryColumns.customColumn3.computedValue" + }, + { + "key": "primaryColumns.customColumn4.computedValue" + }, + { + "key": "primaryColumns.customColumn5.computedValue" + }, + { + "key": "primaryColumns.customColumn6.computedValue" + }, + { + "key": "primaryColumns.customColumn7.computedValue" + }, + { + "key": "primaryColumns.customColumn8.computedValue" + }, + { + "key": "primaryColumns.customColumn2.buttonLabel" + } + ], + "leftColumn": 0, + "primaryColumns": { + "customColumn7": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.author?.email})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 8, + "isVisible": true, + "label": "Author", + "columnType": "text", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn7", + "buttonLabelColor": "#FFFFFF", + "verticalAlignment": "CENTER" + }, + "page_id": { + "isDerived": false, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.page_id})}}", + "textSize": "PARAGRAPH2", + "index": 1, + "isVisible": false, + "label": "page_id", + "columnType": "text", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "page_id", + "verticalAlignment": "CENTER" + }, + "customColumn8": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.updated_at?.date.start})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 9, + "isVisible": true, + "label": "Last Updated", + "inputFormat": "YYYY-MM-DD", + "columnType": "date", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn8", + "buttonLabelColor": "#FFFFFF", + "outputFormat": "LL", + "verticalAlignment": "CENTER" + }, + "data": { + "isDerived": false, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return '#' + currentRow.data.id?.number})}}", + "textSize": "PARAGRAPH2", + "index": 0, + "isVisible": true, + "label": "ID", + "fontStyle": "BOLD", + "columnType": "text", + "horizontalAlignment": "RIGHT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "data", + "verticalAlignment": "CENTER" + }, + "customColumn2": { + "isDerived": true, + "computedValue": "", + "onClick": "{{showModal('Modal2')}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "#38AFF4", + "index": 9, + "isVisible": true, + "label": "Re-Estimate", + "buttonLabel": "{{Table2.sanitizedTableData.map((currentRow) => { return 'Update'})}}", + "columnType": "button", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn2", + "buttonLabelColor": "#F6F7F8", + "verticalAlignment": "CENTER" + }, + "customColumn3": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.estimate?.number})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 4, + "isVisible": true, + "label": "Estimate(SP)", + "columnType": "number", + "horizontalAlignment": "RIGHT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn3", + "buttonLabelColor": "#FFFFFF", + "verticalAlignment": "CENTER" + }, + "customColumn4": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.issue_type?.select.name})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 5, + "isVisible": true, + "label": "Issue Type", + "columnType": "text", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn4", + "buttonLabelColor": "#FFFFFF", + "verticalAlignment": "CENTER" + }, + "customColumn5": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.issue_url?.url})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 6, + "isVisible": true, + "label": "Issue URL", + "columnType": "url", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn5", + "buttonLabelColor": "#FFFFFF", + "verticalAlignment": "CENTER" + }, + "customColumn6": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.created_at?.date.start})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 7, + "isVisible": true, + "label": "Created At", + "inputFormat": "YYYY-MM-DD", + "columnType": "date", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn6", + "buttonLabelColor": "#FFFFFF", + "outputFormat": "LL", + "verticalAlignment": "CENTER" + } + }, + "derivedColumns": { + "customColumn7": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.author?.email})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 8, + "isVisible": true, + "label": "Author", + "columnType": "text", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn7", + "buttonLabelColor": "#FFFFFF", + "verticalAlignment": "CENTER" + }, + "customColumn8": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.updated_at?.date.start})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 9, + "isVisible": true, + "label": "Last Updated", + "inputFormat": "YYYY-MM-DD", + "columnType": "date", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn8", + "buttonLabelColor": "#FFFFFF", + "outputFormat": "LL", + "verticalAlignment": "CENTER" + }, + "customColumn2": { + "isDerived": true, + "computedValue": "", + "onClick": "{{showModal('Modal2')}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "#38AFF4", + "index": 9, + "isVisible": true, + "label": "Re-Estimate", + "buttonLabel": "{{Table1.sanitizedTableData.map((currentRow) => { return 'Update'})}}", + "columnType": "button", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn2", + "buttonLabelColor": "#F6F7F8", + "verticalAlignment": "CENTER" + }, + "customColumn3": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.estimate?.number})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 4, + "isVisible": true, + "label": "Estimate(SP)", + "columnType": "number", + "horizontalAlignment": "RIGHT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn3", + "buttonLabelColor": "#FFFFFF", + "verticalAlignment": "CENTER" + }, + "customColumn4": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.issue_type?.select.name})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 5, + "isVisible": true, + "label": "Issue Type", + "columnType": "text", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn4", + "buttonLabelColor": "#FFFFFF", + "verticalAlignment": "CENTER" + }, + "customColumn5": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.issue_url?.url})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 6, + "isVisible": true, + "label": "Issue URL", + "columnType": "url", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn5", + "buttonLabelColor": "#FFFFFF", + "verticalAlignment": "CENTER" + }, + "customColumn6": { + "isDerived": true, + "computedValue": "{{Table2.sanitizedTableData.map((currentRow) => { return currentRow.data.created_at?.date.start})}}", + "textSize": "PARAGRAPH2", + "buttonStyle": "rgb(3, 179, 101)", + "index": 7, + "isVisible": true, + "label": "Created At", + "inputFormat": "YYYY-MM-DD", + "columnType": "date", + "horizontalAlignment": "LEFT", + "width": 150, + "enableFilter": true, + "enableSort": true, + "id": "customColumn6", + "buttonLabelColor": "#FFFFFF", + "outputFormat": "LL", + "verticalAlignment": "CENTER" + } + }, + "rightColumn": 64, + "textSize": "PARAGRAPH2", + "widgetId": "efhhtr442b", + "tableData": "{{[]}}", + "isVisible": true, + "label": "Data", + "searchKey": "", + "version": 1, + "parentId": "9kax26wjp6", + "isLoading": false, + "horizontalAlignment": "LEFT", + "verticalAlignment": "CENTER", + "columnSizeMap": { + "parent": 136, + "issue_url": 174, + "data": 72, + "author": 174, + "issue_type": 109, + "created_at": 117, + "customColumn1": 122, + "customColumn2": 110, + "customColumn3": 110, + "customColumn4": 112, + "customColumn5": 320, + "task": 245, + "estimate": 109, + "step": 62, + "id": 60, + "status": 75 + } + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/tableWidgetDsl.json b/app/client/cypress/fixtures/tableWidgetDsl.json index 349f604903..9929225f5e 100644 --- a/app/client/cypress/fixtures/tableWidgetDsl.json +++ b/app/client/cypress/fixtures/tableWidgetDsl.json @@ -67,7 +67,11 @@ "bottomRow": 10, "parentId": "tyiwk4xuq0", "widgetId": "5up3r2iuvs", - "dynamicBindingPathList": [] + "dynamicTriggerPathList": [{ + "key": "onSearchTextChanged" + }], + "onSearchTextChanged": "{{Api1.run()}}", + "selectedRow": "{{Table1.tableData[Table1.selectedRowIndex]}}" } ], "widgetId": "tyiwk4xuq0", diff --git a/app/client/cypress/fixtures/tabsWithWidgetDsl.json b/app/client/cypress/fixtures/tabsWithWidgetDsl.json new file mode 100644 index 0000000000..f736348b0b --- /dev/null +++ b/app/client/cypress/fixtures/tabsWithWidgetDsl.json @@ -0,0 +1,208 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 966, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1280, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 18, + "minHeight": 1292, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "isVisible": true, + "shouldScrollContents": false, + "widgetName": "Tabs1", + "tabsObj": { + "tab1": { + "label": "Tab 1", + "id": "tab1", + "widgetId": "78u8fmjgom", + "isVisible": true, + "index": 0 + }, + "tab2": { + "label": "Tab 2", + "id": "tab2", + "widgetId": "tch1asw4ti", + "isVisible": true, + "index": 1 + } + }, + "shouldShowTabs": true, + "defaultTab": "Tab 1", + "version": 2, + "type": "TABS_WIDGET", + "isLoading": false, + "parentColumnSpace": 57.875, + "parentRowSpace": 40, + "leftColumn": 3, + "rightColumn": 15, + "topRow": 1, + "bottomRow": 10, + "parentId": "0", + "widgetId": "wqb436vlj8", + "children": [ + { + "type": "CANVAS_WIDGET", + "tabId": "tab1", + "tabName": "Tab 1", + "widgetId": "78u8fmjgom", + "parentId": "wqb436vlj8", + "detachFromLayout": true, + "children": [ + { + "isVisible": true, + "isDisabled": false, + "datePickerType": "DATE_PICKER", + "label": "", + "dateFormat": "YYYY-MM-DD HH:mm", + "widgetName": "DatePicker1", + "defaultDate": "2021-05-09T10:22:42.862Z", + "version": 2, + "isRequired": false, + "type": "DATE_PICKER_WIDGET2", + "isLoading": false, + "parentColumnSpace": 40.90625, + "parentRowSpace": 40, + "leftColumn": 2, + "rightColumn": 7, + "topRow": 0, + "bottomRow": 1, + "parentId": "78u8fmjgom", + "widgetId": "opw72wgf4p" + }, + { + "isVisible": true, + "text": "Label", + "fontSize": "PARAGRAPH", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "widgetName": "Text1", + "version": 1, + "type": "TEXT_WIDGET", + "isLoading": false, + "parentColumnSpace": 40.90625, + "parentRowSpace": 40, + "leftColumn": 2, + "rightColumn": 6, + "topRow": 2, + "bottomRow": 3, + "parentId": "78u8fmjgom", + "widgetId": "vnm91359up" + }, + { + "isVisible": true, + "text": "Submit", + "buttonStyle": "PRIMARY_BUTTON", + "widgetName": "Button1", + "isDisabled": false, + "isDefaultClickDisabled": true, + "version": 1, + "type": "BUTTON_WIDGET", + "isLoading": false, + "parentColumnSpace": 40.90625, + "parentRowSpace": 40, + "leftColumn": 3, + "rightColumn": 5, + "topRow": 4, + "bottomRow": 5, + "parentId": "78u8fmjgom", + "widgetId": "lu7vjd5tq3" + } + ], + "parentRowSpace": 1, + "parentColumnSpace": 1, + "leftColumn": 0, + "rightColumn": 463, + "topRow": 0, + "bottomRow": 280, + "isLoading": false, + "widgetName": "Canvas1", + "renderMode": "CANVAS" + }, + { + "type": "CANVAS_WIDGET", + "tabId": "tab2", + "tabName": "Tab 2", + "widgetId": "tch1asw4ti", + "parentId": "wqb436vlj8", + "detachFromLayout": true, + "children": [ + { + "isVisible": true, + "label": "Label", + "defaultCheckedState": true, + "widgetName": "Checkbox1", + "version": 1, + "alignWidget": "LEFT", + "isDisabled": false, + "isRequired": false, + "type": "CHECKBOX_WIDGET", + "isLoading": false, + "parentColumnSpace": 40.90625, + "parentRowSpace": 40, + "leftColumn": 3, + "rightColumn": 6, + "topRow": 0, + "bottomRow": 1, + "parentId": "tch1asw4ti", + "widgetId": "l7ahbr0vww" + }, + { + "isVisible": true, + "label": "", + "options": [ + { + "label": "Yes", + "value": "Y" + }, + { + "label": "No", + "value": "N" + } + ], + "defaultOptionValue": "Y", + "widgetName": "RadioGroup1", + "version": 1, + "isRequired": false, + "isDisabled": false, + "type": "RADIO_GROUP_WIDGET", + "isLoading": false, + "parentColumnSpace": 40.90625, + "parentRowSpace": 40, + "leftColumn": 8, + "rightColumn": 11, + "topRow": 0, + "bottomRow": 2, + "parentId": "tch1asw4ti", + "widgetId": "k4vwuehj29" + } + ], + "parentRowSpace": 1, + "parentColumnSpace": 1, + "leftColumn": 0, + "rightColumn": 463, + "topRow": 0, + "bottomRow": 160, + "isLoading": false, + "widgetName": "Canvas2", + "renderMode": "CANVAS" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/testdata.json b/app/client/cypress/fixtures/testdata.json index 622aac0aae..af51c97ad5 100644 --- a/app/client/cypress/fixtures/testdata.json +++ b/app/client/cypress/fixtures/testdata.json @@ -112,8 +112,8 @@ "currentRowWithIdOutside": "#{{currentRow.email}}", "currentRowWithIdInside": "{{\"#\" + currentRow.email}}", "defaultdataBinding": "{'Test','This'}", - "dropdownErrorMsg": "This value does not evaluate to type \"Array<{ label: string, value: string }>\". Transform it using JS inside '{{ }}'", - "tableWidgetErrorMsg": "This value does not evaluate to type \"Array\". Transform it using JS inside '{{ }}'", + "dropdownErrorMsg": "This value does not evaluate to type \"Array<{ label: string, value: string }>\".", + "tableWidgetErrorMsg": "This value does not evaluate to type \"Array\".", "bindingDataPoint": "{{JSON.stringify(Chart1.selectedDataPoint)}}", "bindChartData": "{{Chart1.selectedDataPoint", "appUrl": "https://oauth.mocklab.io/userinfo", @@ -121,5 +121,7 @@ "clientSecret": "505dac16a21681f277b5fde97445be18", "accessTokenUrl": "https://oauth.mocklab.io/oauth/token", "oauthResponse": "169444434892406", - "authorizationURL": "https://oauth.mocklab.io/oauth/authorize" + "authorizationURL": "https://oauth.mocklab.io/oauth/authorize", + "basicURl": "https://envyenksqii9nf3.m.pipedream.net", + "methodWithQueryParam": "users?q=mimeType='application/vnd.google-apps.spreadsheet'" } diff --git a/app/client/cypress/fixtures/widgetSelection.json b/app/client/cypress/fixtures/widgetSelection.json new file mode 100644 index 0000000000..2347e0629b --- /dev/null +++ b/app/client/cypress/fixtures/widgetSelection.json @@ -0,0 +1,209 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1270, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 820, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 23, + "minHeight": 830, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "widgetName": "Chart1", + "rightColumn": 26, + "allowHorizontalScroll": false, + "widgetId": "ypstklohw5", + "topRow": 4, + "bottomRow": 36, + "parentRowSpace": 10, + "isVisible": true, + "type": "CHART_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "chartData": { + "3jzahcrorq": { + "seriesName": "Sales", + "data": [ + { + "x": "Mon", + "y": 10000 + }, + { + "x": "Tue", + "y": 12000 + }, + { + "x": "Wed", + "y": 32000 + }, + { + "x": "Thu", + "y": 28000 + }, + { + "x": "Fri", + "y": 14000 + }, + { + "x": "Sat", + "y": 19000 + }, + { + "x": "Sun", + "y": 36000 + } + ] + } + }, + "yAxisName": "Total Order Revenue $", + "parentColumnSpace": 19.65625, + "chartName": "Last week's revenue", + "leftColumn": 2, + "xAxisName": "Last Week", + "customFusionChartConfig": { + "type": "column2d", + "dataSource": { + "chart": { + "caption": "Last week's revenue", + "xAxisName": "Last Week", + "yAxisName": "Total Order Revenue $", + "theme": "fusion" + }, + "data": [ + { + "label": "Mon", + "value": 10000 + }, + { + "label": "Tue", + "value": 12000 + }, + { + "label": "Wed", + "value": 32000 + }, + { + "label": "Thu", + "value": 28000 + }, + { + "label": "Fri", + "value": 14000 + }, + { + "label": "Sat", + "value": 19000 + }, + { + "label": "Sun", + "value": 36000 + } + ], + "trendlines": [ + { + "line": [ + { + "startvalue": "38000", + "valueOnRight": "1", + "displayvalue": "Weekly Target" + } + ] + } + ] + } + }, + "chartType": "LINE_CHART" + }, + { + "backgroundColor": "#FFFFFF", + "widgetName": "Container1", + "rightColumn": 62, + "widgetId": "6y21iarlp4", + "containerStyle": "card", + "topRow": 11, + "bottomRow": 51, + "parentRowSpace": 10, + "isVisible": true, + "type": "CONTAINER_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "parentColumnSpace": 19.65625, + "leftColumn": 30, + "children": [ + { + "widgetName": "Canvas1", + "rightColumn": 629, + "detachFromLayout": true, + "widgetId": "jqasr1uss5", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 400, + "parentRowSpace": 1, + "isVisible": true, + "canExtend": false, + "type": "CANVAS_WIDGET", + "version": 1, + "parentId": "6y21iarlp4", + "minHeight": 400, + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "children": [] + } + ] + }, + { + "image": "", + "widgetName": "Image1", + "rightColumn": 22, + "widgetId": "1t50avy6f1", + "topRow": 58, + "bottomRow": 70, + "parentRowSpace": 10, + "isVisible": true, + "type": "IMAGE_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "maxZoomLevel": 1, + "parentColumnSpace": 19.65625, + "imageShape": "RECTANGLE", + "leftColumn": 6, + "defaultImage": "https://res.cloudinary.com/drako999/image/upload/v1589196259/default.png" + }, + { + "widgetName": "Button1", + "rightColumn": 18, + "isDefaultClickDisabled": true, + "widgetId": "41wgbhd5vp", + "buttonStyle": "PRIMARY_BUTTON", + "topRow": 44, + "bottomRow": 48, + "parentRowSpace": 10, + "isVisible": true, + "type": "BUTTON_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "parentColumnSpace": 19.65625, + "leftColumn": 10, + "text": "Submit", + "isDisabled": false + } + ] + } + } \ No newline at end of file diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ActionExecution/ExecutionParams_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ActionExecution/ExecutionParams_spec.js index 6e4a234a3b..4a50dfc688 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ActionExecution/ExecutionParams_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ActionExecution/ExecutionParams_spec.js @@ -31,8 +31,12 @@ describe("API Panel Test Functionality", function() { cy.contains(".t--datasource-name", datasourceName) .find(queryLocators.createQuery) .click(); - cy.get(queryLocators.templateMenu).click(); + cy.get(queryLocators.settings).click({ force: true }); + cy.get(queryLocators.switch) + .last() + .click({ force: true }); + cy.get(queryLocators.query).click({ force: true }); cy.get(".CodeMirror textarea") .first() .focus() @@ -43,6 +47,7 @@ describe("API Panel Test Functionality", function() { cy.WaitAutoSave(); cy.runQuery(); }); + it("Will pass execution params", function() { // Bind the table cy.SearchEntityandOpen("Table1"); @@ -78,7 +83,6 @@ describe("API Panel Test Functionality", function() { // Publish the app cy.PublishtheApp(); - cy.wait("@postExecute"); // Assert on load data in table cy.readTabledataPublish("0", "1").then((cellData) => { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js index d46a3b1ec0..7ffd0131ad 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Edit_spec.js @@ -7,11 +7,9 @@ describe("API Panel Test Functionality", function() { cy.NavigateToAPI_Panel(); cy.log("Navigation to API Panel screen successful"); cy.CreateAPI("FirstAPI"); - cy.get("textarea").should( - "have.attr", - "placeholder", - "https://mock-api.appsmith.com/users", - ); + cy.get(".CodeMirror-placeholder") + .first() + .should("have.text", "https://mock-api.appsmith.com/users"); cy.log("Creation of FirstAPI Action successful"); cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods); cy.SaveAndRunAPI(); @@ -29,14 +27,24 @@ describe("API Panel Test Functionality", function() { cy.NavigateToAPI_Panel(); cy.CreateAPI("CrashTestAPI"); cy.SelectAction(testdata.postAction); - cy.xpath(apiwidget.headerKey) + cy.get(apiwidget.headerKey) .first() - .focus({ force: true }) - .type("{uparrow}", { force: true }) - .type("{ctrl}{shift}{downarrow}", { force: true }) - .type("{backspace}", { force: true }); + .click({ force: true }) + .type("{uparrow}", { parseSpecialCharSequences: true }) + .type("{ctrl}{shift}{downarrow}", { parseSpecialCharSequences: true }) + .type("{backspace}", { parseSpecialCharSequences: true }); // assert so that this fails - cy.xpath(apiwidget.headerKey).should("be.visible"); - cy.xpath(apiwidget.headerKey).should("have.value", ""); + cy.get(apiwidget.headerKey).should("be.visible"); + cy.get(apiwidget.headerKey).should("have.value", ""); + }); + + it("Should correctly parse query params", function() { + cy.NavigateToAPI_Panel(); + cy.CreateAPI("APIWithQueryParams"); + cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methodWithQueryParam); + cy.ValidateQueryParams({ + key: "q", + value: "mimeType='application/vnd.google-apps.spreadsheet'", + }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_RequestBody_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_RequestBody_spec.js index e7bcd32a12..0651e60673 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_RequestBody_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_RequestBody_spec.js @@ -12,7 +12,7 @@ describe("API Panel request body", function() { cy.contains(apiEditor.bodyTab).click(); cy.contains(testdata.apiFormDataBodyType).click(); - cy.xpath(apiwidget.formEncoded).should("be.visible"); + cy.get(apiwidget.formEncoded).should("be.visible"); cy.DeleteAPI(); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Unique_name_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Unique_name_spec.js index 8e784f5f56..48655115a4 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Unique_name_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Unique_name_spec.js @@ -3,9 +3,15 @@ describe("Name uniqueness test", function() { cy.log("Login Successful"); cy.NavigateToAPI_Panel(); cy.log("Navigation to API Panel screen successful"); - cy.CreateAPI("UniqueName"); + cy.CreateAPI("Uni"); cy.log("Creation of UniqueName Action successful"); - cy.CreationOfUniqueAPIcheck("UniqueName"); + }); + + it("Validate Unique Name", () => { + cy.CreationOfUniqueAPIcheck("Uni"); + }); + + it("Validate download apiname check", () => { cy.CreationOfUniqueAPIcheck("download"); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/DuplicateApplication_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/DuplicateApplication_spec.js index e824916c33..f9aba00e65 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/DuplicateApplication_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/DuplicateApplication_spec.js @@ -1,8 +1,10 @@ -const dsl = require("../../../../fixtures/displayWidgetDsl.json"); +const dsl = require("../../../../fixtures/basicDsl.json"); const homePage = require("../../../../locators/HomePage.json"); const commonlocators = require("../../../../locators/commonlocators.json"); -const explorerlocators = require("../../../../locators/explorerlocators.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); + let duplicateApplicationDsl; +let parentApplicationDsl; describe("Duplicate application", function() { before(() => { @@ -10,12 +12,20 @@ describe("Duplicate application", function() { }); it("Check whether the duplicate application has the same dsl as the original", function() { - cy.get(commonlocators.homeIcon).click({ force: true }); const appname = localStorage.getItem("AppName"); + cy.SearchEntityandOpen("Input1"); + cy.intercept("PUT", "/api/v1/layouts/*/pages/*").as("inputUpdate"); + cy.get(widgetsPage.defaultInput).type("A"); + cy.get(commonlocators.editPropCrossButton).click({ force: true }); + cy.wait("@inputUpdate").then((response) => { + parentApplicationDsl = response.response.body.data.dsl; + }); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(2000); + cy.NavigateToHome(); cy.get(homePage.searchInput).type(appname); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(2000); - cy.get(homePage.applicationCard) .first() .trigger("mouseover"); @@ -23,17 +33,20 @@ describe("Duplicate application", function() { .first() .click({ force: true }); cy.get(homePage.duplicateApp).click({ force: true }); - + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(4000); cy.wait("@getPage").should( "have.nested.property", "response.body.responseMeta.status", 200, ); cy.get("@getPage").then((httpResponse) => { - const data = httpResponse.response.body.data; - duplicateApplicationDsl = data.layouts[0].dsl; - - expect(duplicateApplicationDsl).to.deep.equal(dsl.dsl); + duplicateApplicationDsl = httpResponse.response.body.data.layouts[0].dsl; + cy.log(JSON.stringify(duplicateApplicationDsl)); + cy.log(JSON.stringify(parentApplicationDsl)); + expect(JSON.stringify(duplicateApplicationDsl)).to.deep.equal( + JSON.stringify(parentApplicationDsl), + ); }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js new file mode 100644 index 0000000000..3b58ec8dde --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js @@ -0,0 +1,159 @@ +const dsl = require("../../../../fixtures/displayWidgetDsl.json"); +const homePage = require("../../../../locators/HomePage.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Export application as a JSON file", function() { + let orgid; + let appid; + let currentUrl; + let newOrganizationName; + let appname; + + before(() => { + cy.addDsl(dsl); + }); + + it("Check if exporting app flow works as expected", function() { + cy.get(commonlocators.homeIcon).click({ force: true }); + appname = localStorage.getItem("AppName"); + cy.get(homePage.searchInput).type(appname); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(2000); + + cy.get(homePage.applicationCard) + .first() + .trigger("mouseover"); + cy.get(homePage.appMoreIcon) + .first() + .click({ force: true }); + cy.get(homePage.exportAppFromMenu).click({ force: true }); + cy.get(homePage.toastMessage).should("contain", "Successfully exported"); + cy.LogOut(); + }); + + it("User with admin access,should be able to export the app", function() { + cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); + cy.NavigateToHome(); + cy.generateUUID().then((uid) => { + orgid = uid; + appid = uid; + localStorage.setItem("OrgName", orgid); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + }); + cy.CreateAppForOrg(orgid, appid); + cy.wait("@getPagesForCreateApp").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.get("h2").contains("Drag and drop a widget here"); + cy.get(homePage.shareApp).click({ force: true }); + cy.shareApp(Cypress.env("TESTUSERNAME1"), homePage.adminRole); + + cy.LogOut(); + + cy.LogintoApp(Cypress.env("TESTUSERNAME1"), Cypress.env("TESTPASSWORD1")); + cy.NavigateToHome(); + cy.wait(2000); + cy.log({ appid }); + cy.get(homePage.searchInput).type(appid); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(2000); + + cy.get(homePage.applicationCard) + .first() + .trigger("mouseover"); + cy.get(homePage.appMoreIcon) + .first() + .click({ force: true }); + cy.get(homePage.exportAppFromMenu).should("be.visible"); + }); + cy.LogOut(); + }); + + it("User with developer access,should not be able to export the app", function() { + cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); + cy.NavigateToHome(); + cy.generateUUID().then((uid) => { + orgid = uid; + appid = uid; + localStorage.setItem("OrgName", orgid); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + }); + cy.CreateAppForOrg(orgid, appid); + cy.wait("@getPagesForCreateApp").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.get("h2").contains("Drag and drop a widget here"); + cy.get(homePage.shareApp).click({ force: true }); + cy.shareApp(Cypress.env("TESTUSERNAME1"), homePage.developerRole); + + cy.LogOut(); + + cy.LogintoApp(Cypress.env("TESTUSERNAME1"), Cypress.env("TESTPASSWORD1")); + cy.NavigateToHome(); + cy.wait(2000); + cy.log({ appid }); + cy.get(homePage.searchInput).type(appid); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(2000); + + cy.get(homePage.applicationCard) + .first() + .trigger("mouseover"); + cy.get(homePage.appMoreIcon) + .first() + .click({ force: true }); + cy.get(homePage.exportAppFromMenu).should("not.exist"); + }); + cy.LogOut(); + }); + + it("User with viewer access,should not be able to export the app", function() { + cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); + cy.NavigateToHome(); + cy.generateUUID().then((uid) => { + orgid = uid; + appid = uid; + localStorage.setItem("OrgName", orgid); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + }); + cy.CreateAppForOrg(orgid, appid); + cy.wait("@getPagesForCreateApp").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.get("h2").contains("Drag and drop a widget here"); + cy.get(homePage.shareApp).click({ force: true }); + cy.shareApp(Cypress.env("TESTUSERNAME1"), homePage.viewerRole); + + cy.LogOut(); + + cy.LogintoApp(Cypress.env("TESTUSERNAME1"), Cypress.env("TESTPASSWORD1")); + cy.NavigateToHome(); + cy.wait(2000); + cy.log({ appid }); + cy.get(homePage.searchInput).type(appid); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(2000); + + cy.get(homePage.applicationCard) + .first() + .trigger("mouseover"); + cy.get(homePage.appEditIcon).should("not.exist"); + }); + cy.LogOut(); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ForkApplication_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ForkApplication_spec.js index f0d29008c1..9cc80fe443 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ForkApplication_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ForkApplication_spec.js @@ -1,7 +1,10 @@ -const dsl = require("../../../../fixtures/displayWidgetDsl.json"); +const dsl = require("../../../../fixtures/basicDsl.json"); const homePage = require("../../../../locators/HomePage.json"); const commonlocators = require("../../../../locators/commonlocators.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); + let forkedApplicationDsl; +let parentApplicationDsl; describe("Fork application across orgs", function() { before(() => { @@ -9,12 +12,20 @@ describe("Fork application across orgs", function() { }); it("Check if the forked application has the same dsl as the original", function() { - cy.get(commonlocators.homeIcon).click({ force: true }); const appname = localStorage.getItem("AppName"); + cy.SearchEntityandOpen("Input1"); + cy.intercept("PUT", "/api/v1/layouts/*/pages/*").as("inputUpdate"); + cy.get(widgetsPage.defaultInput).type("A"); + cy.get(commonlocators.editPropCrossButton).click({ force: true }); + cy.wait("@inputUpdate").then((response) => { + parentApplicationDsl = response.response.body.data.dsl; + }); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(2000); + cy.NavigateToHome(); cy.get(homePage.searchInput).type(appname); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(2000); - cy.get(homePage.applicationCard) .first() .trigger("mouseover"); @@ -28,6 +39,8 @@ describe("Fork application across orgs", function() { .last() .click({ force: true }); cy.get(homePage.forkAppOrgButton).click({ force: true }); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(4000); cy.wait("@postForkAppOrg").then((httpResponse) => { expect(httpResponse.status).to.equal(200); }); @@ -35,8 +48,11 @@ describe("Fork application across orgs", function() { cy.get("@getPage").then((httpResponse) => { const data = httpResponse.response.body.data; forkedApplicationDsl = data.layouts[0].dsl; - - expect(forkedApplicationDsl).to.deep.equal(dsl.dsl); + cy.log(JSON.stringify(forkedApplicationDsl)); + cy.log(JSON.stringify(parentApplicationDsl)); + expect(JSON.stringify(forkedApplicationDsl)).to.contain( + JSON.stringify(parentApplicationDsl), + ); }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/UpdateApplication_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/UpdateApplication_spec.js index cca7436c89..fa111127a3 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/UpdateApplication_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/UpdateApplication_spec.js @@ -100,12 +100,13 @@ describe("Update Application", function() { ); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(2000); - + cy.get(homePage.searchInput).type(veryLongAppName); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(2000); cy.get(homePage.applicationCard) .first() .find(homePage.applicationCardName) .trigger("mouseover"); - cy.get(".bp3-popover-target.bp3-popover-open").should("have.length", 1); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_API_with_List_Widget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_API_with_List_Widget_spec.js new file mode 100644 index 0000000000..4015b794e4 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_API_with_List_Widget_spec.js @@ -0,0 +1,94 @@ +const commonlocators = require("../../../../locators/commonlocators.json"); +const dsl = require("../../../../fixtures/listwidgetdsl.json"); +const pages = require("../../../../locators/Pages.json"); +const apiPage = require("../../../../locators/ApiEditor.json"); +const publishPage = require("../../../../locators/publishWidgetspage.json"); + +describe("Test Create Api and Bind to Table widget", function() { + let apiData; + let valueToTest; + before(() => { + cy.addDsl(dsl); + }); + + it("Test_Add users api and execute api", function() { + cy.createAndFillApi(this.data.userApi, "/users"); + cy.RunAPI(); + cy.get(apiPage.responseBody) + .contains("name") + .siblings("span") + .invoke("text") + .then((text) => { + valueToTest = `${text + .match(/"(.*)"/)[0] + .split('"') + .join("")}`; + cy.log(valueToTest); + apiData = valueToTest; + cy.log("val1:" + valueToTest); + }); + }); + + it("Test_Validate the Api data is updated on List widget", function() { + cy.SearchEntityandOpen("List1"); + cy.getCodeMirror().then(($cm) => { + cy.get(".CodeMirror textarea") + .first() + .type(`{{Api1.data.users}}`, { + force: true, + parseSpecialCharSequences: false, + }); + }); + cy.get(commonlocators.editPropCrossButton).click({ force: true }); + cy.get(".t--draggable-textwidget span").should("have.length", 4); + + cy.get(".t--draggable-textwidget span") + .first() + .invoke("text") + .then((text) => { + expect(text).to.equal(valueToTest); + }); + cy.PublishtheApp(); + cy.get(".t--widget-textwidget span").should("have.length", 4); + cy.get(".t--widget-textwidget span") + .first() + .invoke("text") + .then((text) => { + expect(text).to.equal(valueToTest); + }); + }); + + it("Test_Validate the list widget ", function() { + cy.get(publishPage.backToEditor).click({ force: true }); + cy.SearchEntityandOpen("List1"); + + cy.getCodeMirror().then(($cm) => { + cy.get(".CodeMirror textarea") + .last() + .type(`50`, { + force: true, + parseSpecialCharSequences: false, + }); + }); + cy.get(commonlocators.editPropCrossButton).click({ force: true }); + cy.get(".t--draggable-textwidget span").should("have.length", 2); + cy.get(".t--draggable-textwidget span") + .first() + .invoke("text") + .then((text) => { + expect(text).to.equal(valueToTest); + }); + cy.PublishtheApp(); + cy.get(".t--widget-textwidget span").should("have.length", 2); + cy.get(".t--widget-textwidget span") + .first() + .invoke("text") + .then((text) => { + expect(text).to.equal(valueToTest); + }); + }); + + afterEach(() => { + // put your clean up code if any + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_InputWidget_Table_Sorting_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_InputWidget_Table_Sorting_spec.js index adab4c0052..7583ac1814 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_InputWidget_Table_Sorting_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_InputWidget_Table_Sorting_spec.js @@ -28,6 +28,7 @@ describe("Binding the Table and input Widget", function() { cy.get(".draggable-header ") .first() .click({ force: true }); + cy.wait(1000); cy.readTabledataPublish("0", "0").then((tabData) => { const tabValue = tabData; expect(tabValue).to.be.equal("6788734"); @@ -40,6 +41,7 @@ describe("Binding the Table and input Widget", function() { cy.get(".draggable-header ") .first() .click({ force: true }); + cy.wait(1000); cy.readTabledataPublish("0", "0").then((tabData) => { const tabValue = tabData; expect(tabValue).to.be.equal("2381224"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_TableTextPagination_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_TableTextPagination_spec.js index 4b128ed56b..707a039195 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_TableTextPagination_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_TableTextPagination_spec.js @@ -46,19 +46,18 @@ describe("Test Create Api and Bind to Table widget", function() { it("Table-Text, Validate Publish Mode on Server Side Pagination of Paginate with Table Page No", function() { cy.PublishtheApp(); + // Make sure onPageLoad action has run before validating the data + cy.wait("@postExecute"); cy.ValidatePublishTableData("1"); cy.get(commonlocators.tableNextPage).click({ force: true }); - cy.wait("@postExecute").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); + // Make sure net page action is run + cy.wait("@postExecute"); cy.validateToastMessage("done"); cy.ValidatePublishTableData("11"); - cy.get(publishPage.backToEditor).click({ force: true }); }); it("Test_Add Paginate with Response URL and Execute the Api", function() { + cy.get(publishPage.backToEditor).click({ force: true }); /** Create Api2 of Paginate with Response URL*/ cy.createAndFillApi(this.data.paginationUrl, "users"); cy.RunAPI(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_TableWidget_selectedRow_Input_widget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_TableWidget_selectedRow_Input_widget_spec.js index 44bc0ccf73..cac307625a 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_TableWidget_selectedRow_Input_widget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_TableWidget_selectedRow_Input_widget_spec.js @@ -22,17 +22,6 @@ describe("Binding the table widget and input Widget", function() { ); }); - it("Input widget test with default value from table widget", function() { - cy.SearchEntityandOpen("Input2"); - cy.get(widgetsPage.defaultInput).type(testdata.defaultRowIndexBinding); - cy.get(commonlocators.editPropCrossButton).click({ force: true }); - cy.wait("@updateLayout").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); - }); - it("validation of data displayed in input widgets based on selected row", function() { cy.SearchEntityandOpen("Table1"); cy.get(commonlocators.deflautSelectedRow) @@ -47,10 +36,6 @@ describe("Binding the table widget and input Widget", function() { .first() .invoke("attr", "value") .should("contain", tabValue); - cy.get(publish.inputWidget + " " + "input") - .last() - .invoke("attr", "value") - .should("contain", 2); }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_tableApi_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_tableApi_spec.js index d65d22e80d..fb8eeb6b6e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_tableApi_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_tableApi_spec.js @@ -3,6 +3,7 @@ const dsl = require("../../../../fixtures/tableWidgetDsl.json"); const pages = require("../../../../locators/Pages.json"); const apiPage = require("../../../../locators/ApiEditor.json"); const publishPage = require("../../../../locators/publishWidgetspage.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); describe("Test Create Api and Bind to Table widget", function() { let apiData; @@ -43,6 +44,18 @@ describe("Test Create Api and Bind to Table widget", function() { cy.readTabledataPublish("0", "1").then((tabData) => { expect(apiData).to.eq(`\"${tabData}\"`); }); + cy.get(commonlocators.backToEditor).click(); + }); + + it("Validate onSearchTextChanged function is called when configured for search text", function() { + cy.SearchEntityandOpen("Table1"); + cy.get(".t--widget-tablewidget .t--search-input") + .first() + .type("Currey"); + cy.wait(5000); + cy.readTabledataPublish("0", "1").then((tabData) => { + expect(apiData).to.eq(`\"${tabData}\"`); + }); }); afterEach(() => { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Widgets_form_input_table_default_validation.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Widgets_form_input_table_default_validation.js index 11f615bf69..d9181f25b4 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Widgets_form_input_table_default_validation.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Widgets_form_input_table_default_validation.js @@ -22,18 +22,6 @@ describe("Binding the multiple input Widget", function() { ); }); - it("Binding second input widget with first input widget and validating", function() { - cy.SearchEntityandOpen("Input2"); - cy.get(widgetsPage.defaultInput).type(testdata.defaultMoustacheData); - cy.get(commonlocators.editPropCrossButton).click({ force: true }); - cy.wait("@updateLayout").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); - cy.reload(); - }); - it("validation of data displayed in all widgets based on row selected", function() { cy.isSelectRow(1); cy.readTabledataPublish("1", "0").then((tabData) => { @@ -45,10 +33,10 @@ describe("Binding the multiple input Widget", function() { .first() .invoke("attr", "value") .should("contain", tabValue); - cy.get(publish.inputWidget + " " + "input") - .last() - .invoke("attr", "value") - .should("contain", tabValue); + // cy.get(publish.inputWidget + " " + "input") + // .last() + // .invoke("attr", "value") + // .should("contain", tabValue); }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/aTobAndbToaBasictest_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/aTobAndbToaBasictest_spec.js index 2243383267..9909e7e9a7 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/aTobAndbToaBasictest_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/aTobAndbToaBasictest_spec.js @@ -31,7 +31,6 @@ describe("aTob and bToa library tests ", function() { "response.body.responseMeta.status", 200, ); - cy.reload(); }); it("publish widget and validate the data displayed in input widgets value for aToB and bToa", function() { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/loadashBasictest_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/loadashBasictest_spec.js index 19618536e1..6d30dabefc 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/loadashBasictest_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/loadashBasictest_spec.js @@ -31,7 +31,6 @@ describe("Loadash basic test with input Widget", function() { "response.body.responseMeta.status", 200, ); - cy.reload(); }); it("publish widget and validate the data displayed in input widgets from loadash function", function() { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/momentBasictest_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/momentBasictest_spec.js index 2b6ff63311..19fdd3942e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/momentBasictest_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/momentBasictest_spec.js @@ -31,7 +31,6 @@ describe("Moment basic test with input Widget", function() { "response.body.responseMeta.status", 200, ); - cy.reload(); }); it("publish widget and validate the data displayed in input widgets", function() { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Canvas/Resize_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Canvas/Resize_spec.js index 6e3009fe9a..1f969e2c13 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Canvas/Resize_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Canvas/Resize_spec.js @@ -7,10 +7,17 @@ describe("Canvas Resize", function() { }); it("Deleting bottom widget should resize canvas", function() { const InitHeight = "2960px"; - const FinalHeight = "1292px"; cy.get(commonlocators.dropTarget).should("have.css", "height", InitHeight); cy.openPropertyPane("textwidget"); + cy.intercept("PUT", "/api/v1/layouts/*/pages/*").as("deleteUpdate"); cy.get(commonlocators.deleteWidget).click(); - cy.get(commonlocators.dropTarget).should("have.css", "height", FinalHeight); + cy.wait("@deleteUpdate").then((response) => { + const dsl = response.response.body.data.dsl; + cy.get(commonlocators.dropTarget).should( + "have.css", + "height", + `${dsl.bottomRow}px`, + ); + }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js new file mode 100644 index 0000000000..f565c11a99 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js @@ -0,0 +1,16 @@ +const dsl = require("../../../../fixtures/debuggerDependencyDsl.json"); + +describe("Inspect Entity", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Check whether depedencies and references are shown correctly", function() { + cy.openPropertyPane("inputwidget"); + cy.testJsontext("defaulttext", "{{Button1.text}}"); + + cy.get(".t--debugger").click(); + cy.contains(".react-tabs__tab", "Inspect Entity").click(); + cy.contains(".t--dependencies-item", "Button1").click(); + cy.contains(".t--references-item", "Input1"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js index e884caf535..992fe23a68 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js @@ -1,4 +1,6 @@ const dsl = require("../../../../fixtures/buttondsl.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); +const debuggerLocators = require("../../../../locators/Debugger.json"); describe("Debugger logs", function() { before(() => { @@ -11,4 +13,18 @@ describe("Debugger logs", function() { cy.get(".t--debugger").click(); cy.get(".t--debugger-log-state").contains("Test"); }); + + it("Reset debugger state", function() { + cy.get(".t--property-control-visible") + .find(".t--js-toggle") + .click(); + cy.testJsontext("visible", "Test"); + + cy.get(commonlocators.homeIcon).click({ force: true }); + cy.generateUUID().then((id) => { + cy.CreateAppInFirstListedOrg(id); + + cy.contains(debuggerLocators.debuggerIcon, 0); + }); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js new file mode 100644 index 0000000000..2fd3a73d96 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js @@ -0,0 +1,23 @@ +const dsl = require("../../../../fixtures/debuggerTableDsl.json"); +const explorer = require("../../../../locators/explorerlocators.json"); +const debuggerLocators = require("../../../../locators/Debugger.json"); +const testdata = require("../../../../fixtures/testdata.json"); + +describe("Check debugger logs state when there are onPageLoad actions", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Check debugger logs state when there are onPageLoad actions", function() { + cy.openPropertyPane("tablewidget"); + cy.testJsontext("tabledata", "{{TestApi.data.users}}"); + cy.NavigateToAPI_Panel(); + cy.CreateAPI("TestApi"); + cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods); + cy.SaveAndRunAPI(); + + cy.get(explorer.addWidget).click(); + + cy.reload(); + cy.contains(debuggerLocators.debuggerIcon, 0); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js new file mode 100644 index 0000000000..b90c506c6f --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js @@ -0,0 +1,25 @@ +const dsl = require("../../../../fixtures/buttondsl.json"); +const debuggerLocators = require("../../../../locators/Debugger.json"); + +describe("Widget error state", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Check widget error state", function() { + cy.openPropertyPane("buttonwidget"); + + cy.get(".t--property-control-visible") + .find(".t--js-toggle") + .click(); + cy.testJsontext("visible", "Test"); + + cy.contains(".t--widget-error-count", 1); + }); + + it("Check if the current value is shown in the debugger", function() { + cy.get(debuggerLocators.debuggerIcon).click(); + cy.contains(".react-tabs__tab", "Errors").click(); + + cy.get(debuggerLocators.debuggerLogState).contains("Test"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js index 261a1741fe..d730e42151 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js @@ -1,8 +1,10 @@ const commonlocators = require("../../../../locators/commonlocators.json"); const viewWidgetsPage = require("../../../../locators/ViewWidgets.json"); const publish = require("../../../../locators/publishWidgetspage.json"); -const dsl = require("../../../../fixtures/displayWidgetDsl.json"); +const dsl = require("../../../../fixtures/chartUpdatedDsl.json"); const pages = require("../../../../locators/Pages.json"); +const modalWidgetPage = require("../../../../locators/ModalWidget.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); describe("Chart Widget Functionality", function() { before(() => { @@ -13,7 +15,8 @@ describe("Chart Widget Functionality", function() { cy.openPropertyPane("chartwidget"); }); - it("Chart Widget Functionality", function() { + it("Fill the Chart Widget Properties.", function() { + //changing the Chart Name /** * @param{Text} Random Text * @param{ChartWidget}Mouseover @@ -24,6 +27,7 @@ describe("Chart Widget Functionality", function() { viewWidgetsPage.chartWidget, commonlocators.containerInnerText, ); + //changing the Chart Title /** * @param{Text} Random Input Value */ @@ -32,23 +36,77 @@ describe("Chart Widget Functionality", function() { .contains("App Sign Up") .should("have.text", "App Sign Up"); - cy.get(viewWidgetsPage.chartType) - .last() - .click({ force: true }); + //Entering the Chart data + cy.testJsontext( + "chart-series-data-control", + JSON.stringify(this.data.chartInput), + ); + cy.get(".t--propertypane").click("right"); - cy.get(commonlocators.dropdownmenu) - .children() - .contains("Column Chart") - .click(); - cy.get(viewWidgetsPage.chartType) - .last() - .should("have.text", "Column Chart"); - cy.testJsontext("chartseries", JSON.stringify(this.data.chartInput)); + // Asserting Chart Height cy.get(viewWidgetsPage.chartWidget) .should("be.visible") .and((chart) => { expect(chart.height()).to.be.greaterThan(200); }); + + //Entring the label of x-axis + cy.get(viewWidgetsPage.xlabel) + .click({ force: true }) + .type(this.data.command) + .type(this.data.plan); + //Entring the label of y-axis + cy.get(viewWidgetsPage.ylabel) + .click({ force: true }) + .type(this.data.command) + .type(this.data.ylabel); + + //Close edit prop + cy.get(commonlocators.editPropCrossButton).click({ force: true }); + cy.PublishtheApp(); + }); + + it("Pie Chart Widget Functionality", function() { + //changing the Chart type + cy.UpdateChartType("Pie Chart"); + + //Verifying X-axis labels + cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .last() + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.PieChartLabel) + .eq(k) + .should("have.text", labels[k]); + }); + cy.PublishtheApp(); + }); + + it("Line Chart Widget Functionality", function() { + //changing the Chart type + cy.UpdateChartType("Line Chart"); + + //Verifying X-axis labels + cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .last() + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.Chartlabel) + .eq(k) + .should("have.text", labels[k]); + }); + cy.PublishtheApp(); + }); + + it("Bar Chart Widget Functionality", function() { + //changing the Chart type + cy.UpdateChartType("Bar Chart"); + + //Verifying X-axis labels cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); const labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; [0, 1, 2, 3, 4, 5, 6].forEach((k) => { @@ -59,74 +117,273 @@ describe("Chart Widget Functionality", function() { .eq(k) .should("have.text", labels[k]); }); - cy.get(viewWidgetsPage.xlabel) - .click({ force: true }) - .type(this.data.command) - .type(this.data.plan); - cy.get(viewWidgetsPage.ylabel) - .click({ force: true }) - .type(this.data.command) - .type(this.data.ylabel); - - //Close edit prop - cy.get(commonlocators.editPropCrossButton).click({ force: true }); + cy.PublishtheApp(); }); - it("Chart Widget Functionality To Unchecked Visible Widget", function() { + it("Area Chart Widget Functionality", function() { + //changing the Chart type + cy.UpdateChartType("Area Chart"); + + //Verifying X-axis labels + cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .last() + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.Chartlabel) + .eq(k) + .should("have.text", labels[k]); + }); + cy.PublishtheApp(); + }); + + it("Column Chart Widget Functionality", function() { + //changing the Chart type + cy.UpdateChartType("Column Chart"); + + //Verifying X-axis labels + cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .eq(k) + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.Chartlabel) + .eq(k) + .should("have.text", labels[k]); + }); + cy.PublishtheApp(); + }); + + it("Toggle JS - Pie Chart Widget Functionality", function() { + //changing the Chart type + cy.get(widgetsPage.toggleChartType).click({ force: true }); + cy.testJsontext("charttype", "PIE_CHART"); + + //Verifying X-axis labels + cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .last() + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.PieChartLabel) + .eq(k) + .should("have.text", labels[k]); + }); + cy.PublishtheApp(); + }); + + it("Toggle JS - Line Chart Widget Functionality", function() { + //changing the Chart type + cy.testJsontext("charttype", "LINE_CHART"); + + //Verifying X-axis labels + cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .last() + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.Chartlabel) + .eq(k) + .should("have.text", labels[k]); + }); + cy.PublishtheApp(); + }); + + it("Toggle JS - Bar Chart Widget Functionality", function() { + //changing the Chart type + cy.testJsontext("charttype", "BAR_CHART"); + + //Verifying X-axis labels + cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .eq(k) + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.Chartlabel) + .eq(k) + .should("have.text", labels[k]); + }); + cy.PublishtheApp(); + }); + + it("Toggle JS - Area Chart Widget Functionality", function() { + //changing the Chart type + cy.testJsontext("charttype", "AREA_CHART"); + + //Verifying X-axis labels + cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .last() + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.Chartlabel) + .eq(k) + .should("have.text", labels[k]); + }); + cy.PublishtheApp(); + }); + + it("Toggle JS - Column Chart Widget Functionality", function() { + //changing the Chart type + cy.testJsontext("charttype", "COLUMN_CHART"); + + //Verifying X-axis labels + cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .eq(k) + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.Chartlabel) + .eq(k) + .should("have.text", labels[k]); + }); + cy.PublishtheApp(); + }); + + it("Chart - Show Alert Modal", function() { + //creating the Alert Modal and verify Modal name + cy.createModal("Alert Modal", this.data.AlertModalName); + cy.PublishtheApp(); + cy.get(widgetsPage.chartPlotGroup) + .children() + .first() + .click(); + cy.get(modalWidgetPage.modelTextField).should( + "have.text", + this.data.AlertModalName, + ); + }); + + it("Chart - Form Modal Validation", function() { + //creating the Form Modal and verify Modal name + cy.updateModal("Form Modal", this.data.FormModalName); + cy.PublishtheApp(); + cy.get(widgetsPage.chartPlotGroup) + .children() + .first() + .click(); + cy.get(modalWidgetPage.modelTextField).should( + "have.text", + this.data.FormModalName, + ); + }); + + it("Chart-Unckeck Visible field Validation", function() { + // Making the widget invisible cy.togglebarDisable(commonlocators.visibleCheckbox); cy.PublishtheApp(); cy.get(publish.chartWidget).should("not.exist"); - cy.get(publish.backToEditor).click(); }); - it("Chart Widget Functionality To Check Visible Widget", function() { + + it("Chart-Check Visible field Validation", function() { + // Making the widget visible cy.togglebar(commonlocators.visibleCheckbox); cy.PublishtheApp(); cy.get(publish.chartWidget).should("be.visible"); - cy.get(publish.backToEditor).click(); }); + + it("Toggle JS - Chart-Unckeck Visible field Validation", function() { + //Uncheck the disabled checkbox using JS and validate + cy.get(widgetsPage.toggleVisible).click({ force: true }); + cy.EditWidgetPropertiesUsingJS(widgetsPage.inputToggleVisible, "false"); + cy.PublishtheApp(); + cy.get(publish.chartWidget).should("not.exist"); + }); + + it("Toggle JS - Chart-Check Visible field Validation", function() { + //Check the disabled checkbox using JS and Validate + cy.EditWidgetPropertiesUsingJS(widgetsPage.inputToggleVisible, "true"); + cy.PublishtheApp(); + cy.get(publish.chartWidget).should("be.visible"); + }); + it("Chart Widget Functionality To Uncheck Horizontal Scroll Visible", function() { cy.togglebarDisable(commonlocators.horizontalScroll); cy.PublishtheApp(); cy.get(publish.horizontalTab).should("not.exist"); - cy.get(publish.backToEditor).click(); }); + it("Chart Widget Functionality To Check Horizontal Scroll Visible", function() { cy.togglebar(commonlocators.horizontalScroll); cy.PublishtheApp(); cy.get(publish.horizontalTab) .eq(1) .should("exist"); - cy.get(publish.backToEditor).click(); }); - it("Chart Widget Custom Config Feature", function() { - // Note: This only checks for crashes in custom config - cy.get(viewWidgetsPage.chartType) - .last() - .click({ force: true }); - - cy.get(commonlocators.dropdownmenu) - .children() - .contains("Custom Chart") - .click(); - cy.get(viewWidgetsPage.chartType) - .last() - .should("have.text", "Custom Chart"); + it("Custom Chart Widget Functionality", function() { + //changing the Chart type + cy.get(widgetsPage.toggleChartType).click({ force: true }); + cy.UpdateChartType("Custom Chart"); cy.testJsontext( "customfusionchartconfiguration", `{{${JSON.stringify(this.data.ChartCustomConfig)}}}`, ); - cy.get(viewWidgetsPage.chartWidget) - .should("be.visible") - .and((chart) => { - expect(chart.height()).to.be.greaterThan(200); - }); + + //Verifying X-axis labels cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .eq(k) + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.Chartlabel) + .eq(k) + .should("have.text", labels[k]); + }); + //Close edit prop cy.get(commonlocators.editPropCrossButton).click(); + cy.PublishtheApp(); + }); + + it("Toggle JS - Custom Chart Widget Functionality", function() { + //changing the Chart type + cy.UpdateChartType("Pie Chart"); + cy.get(widgetsPage.toggleChartType).click({ force: true }); + cy.testJsontext("charttype", "CUSTOM_FUSION_CHART"); + + //Verifying X-axis labels + cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1"); + const labels = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]; + [0, 1, 2, 3, 4, 5, 6].forEach((k) => { + cy.get(viewWidgetsPage.rectangleChart) + .eq(k) + .trigger("mousemove", { force: true }); + cy.get(viewWidgetsPage.Chartlabel) + .eq(k) + .should("have.text", labels[k]); + }); + + //Close edit prop + cy.get(commonlocators.editPropCrossButton).click(); + cy.PublishtheApp(); + }); + + it("Chart-Copy Verification", function() { + const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl"; + //Copy Chart and verify all properties + cy.copyWidget("chartwidget", viewWidgetsPage.chartWidget); + + cy.PublishtheApp(); + }); + + it("Chart-Delete Verification", function() { + // Delete the Chart widget + cy.deleteWidget(viewWidgetsPage.chartWidget); + cy.PublishtheApp(); + cy.get(viewWidgetsPage.chartWidget).should("not.exist"); + }); + + afterEach(() => { + cy.get(publish.backToEditor).click({ force: true }); }); }); -afterEach(() => { - // put your clean up code if any -}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Divider_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Divider_spec.js new file mode 100644 index 0000000000..05bcec35a9 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Divider_spec.js @@ -0,0 +1,23 @@ +const dsl = require("../../../../fixtures/DividerDsl.json"); +const explorer = require("../../../../locators/explorerlocators.json"); + +describe("Divider Widget Functionality", function() { + beforeEach(() => { + cy.addDsl(dsl); + }); + + it("Add new Divider", () => { + cy.get(explorer.addWidget).click(); + cy.dragAndDropToCanvas("dividerwidget", { x: 300, y: 300 }); + cy.get(".t--divider-widget").should("exist"); + }); + + it("Open Existing Divider from created Widgets list", () => { + cy.get(".bp3-icon-caret-right ~ .t--entity-name:contains(Widgets)").click({ + multiple: true, + }); + cy.get(".bp3-icon-caret-right ~ .t--entity-name:contains(Divider1)").click({ + multiple: true, + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Image_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Image_spec.js index 747a4e8471..5713afc305 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Image_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Image_spec.js @@ -23,7 +23,6 @@ describe("Image Widget Functionality", function() { ); cy.get(viewWidgetsPage.defaultImage) .click({ force: true }) - .type(this.data.command) .type(this.data.defaultimage); /** * @param{URL} ImageUrl diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Modal_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Modal_spec.js index ba257c5ae2..e36c3f6648 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Modal_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Modal_spec.js @@ -8,7 +8,7 @@ describe("Modal Widget Functionality", function() { it("Add new Modal", () => { cy.get(explorer.addWidget).click(); - cy.dragAndDropToCanvas("modalwidget", { x: 300, y: -300 }); + cy.dragAndDropToCanvas("modalwidget", { x: 300, y: 300 }); cy.get(".t--modal-widget").should("exist"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Column_Resize_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Column_Resize_spec.js new file mode 100644 index 0000000000..7eb9000870 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Column_Resize_spec.js @@ -0,0 +1,24 @@ +/* eslint-disable cypress/no-unnecessary-waiting */ +const widgetsPage = require("../../../../locators/Widgets.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); +const publish = require("../../../../locators/publishWidgetspage.json"); +const dsl = require("../../../../fixtures/tableResizedColumnsDsl.json"); + +describe("Table Widget Functionality with Hidden and Resized Columns", function() { + before(() => { + cy.addDsl(dsl); + }); + + it("Table Widget Functionality with Hidden and Resized Columns", function() { + cy.PublishtheApp(); + cy.get(".t--widget-tablewidget") + .invoke("outerWidth") + .then((tableWidth) => { + cy.get(".t--widget-tablewidget .thead .tr") + .invoke("outerWidth") + .then((width) => { + expect(width).to.be.at.least(tableWidth); + }); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Derived_Column_Data_validation_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Derived_Column_Data_validation_spec.js index 2104fa8e76..d57c23fac3 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Derived_Column_Data_validation_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Derived_Column_Data_validation_spec.js @@ -43,13 +43,12 @@ describe("Test Create Api and Bind to Table widget", function() { // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(1000); cy.toggleJsAndUpdate("tabledata", "Green"); - cy.get(commonlocators.editPropCrossButton).click(); + cy.get(".t--property-pane-back-btn").click({ force: true }); cy.wait("@updateLayout"); cy.readTabledataValidateCSS("1", "0", "background-color", "rgb(0, 128, 0)"); }); it("Edit column name and validate test for computed value based on column type selected", function() { - cy.SearchEntityandOpen("Table1"); cy.editColumn("customColumn1"); cy.readTabledataPublish("1", "9").then((tabData) => { const tabValue = tabData; diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_MultiRowSelect_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_MultiRowSelect_spec.js new file mode 100644 index 0000000000..fc09a9fbe5 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_MultiRowSelect_spec.js @@ -0,0 +1,46 @@ +const widgetsPage = require("../../../../locators/Widgets.json"); +const dsl = require("../../../../fixtures/tableNewDsl.json"); + +describe("Table Widget row multi select validation", function() { + before(() => { + cy.addDsl(dsl); + }); + + it("Test multi select column shows when enableMultirowselection is true", function() { + cy.openPropertyPane("tablewidget"); + cy.get(widgetsPage.toggleEnableMultirowselection) + .first() + .click({ force: true }); + cy.get(".t--table-multiselect-header") + .first() + .should("be.visible"); + + cy.get(".t--table-multiselect") + .first() + .should("be.visible"); + }); + + it("Test click on header cell selects all row", function() { + // click on header check cell + cy.get(".t--table-multiselect-header") + .first() + .click({ force: true }); + // check if rows selected + cy.get(".tr").should("have.class", "selected-row"); + }); + + it("Test click on single row cell changes header select cell state", function() { + // un select all rows + cy.get(".t--table-multiselect-header") + .first() + .click({ force: true }); + // click on first row select box + cy.get(".t--table-multiselect") + .first() + .click({ force: true }); + // check if header cell is in half check state + cy.get(".t--table-multiselect-header-half-check-svg") + .first() + .should("be.visible"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js index e398325e27..15a1cff837 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js @@ -32,19 +32,24 @@ describe("Test Suite to validate copy/paste table Widget", function() { 200, ); cy.get(commonlocators.toastAction).should("be.visible"); + + //Check after copying new table widget should not have any warnings + cy.get('.t--widget-propertypane-toggle [name="warning"]').should( + "not.exist", + ); /* cy.get(commonlocators.toastAction) .contains("UNDO") .click({ force: true }); */ - cy.GlobalSearchEntity("Table2"); + cy.GlobalSearchEntity("Table1Copy"); cy.get(".t--entity-collapse-toggle") .last() .click(); cy.get(apiwidget.propertyList).then(function($lis) { expect($lis).to.have.length(8); - expect($lis.eq(0)).to.contain("{{Table2.selectedRow}}"); - expect($lis.eq(1)).to.contain("{{Table2.selectedRows}}"); + expect($lis.eq(0)).to.contain("{{Table1Copy.selectedRow}}"); + expect($lis.eq(1)).to.contain("{{Table1Copy.selectedRows}}"); }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js index 1bfbed6a6b..d46969722f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js @@ -3,7 +3,6 @@ const widgetsPage = require("../../../../locators/Widgets.json"); const commonlocators = require("../../../../locators/commonlocators.json"); const publish = require("../../../../locators/publishWidgetspage.json"); const dsl = require("../../../../fixtures/tableWidgetDsl.json"); -const pages = require("../../../../locators/Pages.json"); describe("Table Widget Functionality", function() { before(() => { @@ -83,7 +82,9 @@ describe("Table Widget Functionality", function() { cy.wait(5000); cy.get(publish.searchInput) .first() - .clear() + .within(() => { + return cy.get("input").clear(); + }) .type("7434532"); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(1000); @@ -97,7 +98,9 @@ describe("Table Widget Functionality", function() { it("Table Widget Functionality To Filter The Data", function() { cy.get(publish.searchInput) .first() - .clear(); + .within(() => { + return cy.get("input").clear(); + }); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(1000); cy.isSelectRow(1); @@ -261,16 +264,16 @@ describe("Table Widget Functionality", function() { const tabValue = tabData; expect(tabValue).to.be.equal("Lindsay Ferguson"); cy.log("the value is" + tabValue); - cy.get(publish.compactMode).click(); - cy.get(publish.compactOpt) + cy.get(publish.rowHeight).click(); + cy.get(publish.rowHeightOpt) .contains("Tall") .click(); cy.scrollTabledataPublish("3", "3").then((tabData) => { const tabValue = tabData; expect(tabValue).to.be.equal("Byron Fields"); }); - cy.get(publish.compactMode).click(); - cy.get(publish.compactOpt) + cy.get(publish.rowHeight).click(); + cy.get(publish.rowHeightOpt) .contains("Short") .click(); cy.readTabledataPublish("4", "3").then((tabData) => { @@ -282,7 +285,7 @@ describe("Table Widget Functionality", function() { /* To enabled later - + it("Table Widget Functionality To Verify The Visiblity mode functionality", function() { cy.get(publish.backToEditor) .first() diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js index 2732da5b7c..e0e0885887 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js @@ -36,7 +36,19 @@ describe("Text Widget Functionality", function() { .should("have.css", "font-size", "24px"); }); + it("Text Email Parsing Validation", function() { + cy.testCodeMirror("ab.end@domain.com"); + cy.wait("@updateLayout"); + cy.PublishtheApp(); + cy.get(commonlocators.headingTextStyle + " a").should( + "have.attr", + "href", + "mailto:ab.end@domain.com", + ); + }); + it("Text-TextStyle Label Validation", function() { + cy.testCodeMirror(this.data.TextLabelValue); //Changing the Text Style's and validating cy.ChangeTextStyle( this.data.TextLabel, @@ -61,33 +73,33 @@ describe("Text Widget Functionality", function() { .should("have.css", "font-size", "18px"); }); - // it("Text widget depends on itself", function() { - // cy.getCodeMirror().then(($cm) => { - // if ($cm.val() !== "") { - // cy.get(".CodeMirror textarea") - // .first() - // .clear({ - // force: true, - // }); - // } - // - // cy.get(".CodeMirror textarea") - // .first() - // .type(`{{${this.data.TextName}}}`, { - // force: true, - // parseSpecialCharSequences: false, - // }); - // }); - // cy.get(commonlocators.toastBody) - // .first() - // .contains("Cyclic"); - // - // cy.PublishtheApp(); - // cy.get(commonlocators.bodyTextStyle).should( - // "have.text", - // `{{${this.data.TextName}}}`, - // ); - // }); + it("Text widget depends on itself", function() { + cy.getCodeMirror().then(($cm) => { + if ($cm.val() !== "") { + cy.get(".CodeMirror textarea") + .first() + .clear({ + force: true, + }); + } + + cy.get(".CodeMirror textarea") + .first() + .type(`{{${this.data.TextName}}}`, { + force: true, + parseSpecialCharSequences: false, + }); + }); + cy.get(commonlocators.toastBody) + .first() + .contains("Cyclic"); + + cy.PublishtheApp(); + cy.get(commonlocators.bodyTextStyle).should( + "have.text", + `{{${this.data.TextName}}}`, + ); + }); afterEach(() => { cy.get(publishPage.backToEditor).click({ force: true }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/video_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/video_spec.js index 5ee9895da8..1f657612e3 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/video_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/video_spec.js @@ -51,7 +51,7 @@ describe("Video Widget Functionality", function() { cy.get(".CodeMirror textarea") .first() .blur(); - cy.get(widgetsPage.autoPlay).click(); + cy.get(widgetsPage.autoPlay).click({ force: true }); cy.wait("@updateLayout").should( "have.nested.property", "response.body.responseMeta.status", @@ -62,7 +62,7 @@ describe("Video Widget Functionality", function() { cy.get(commonlocators.toastMsg).should("be.visible"); cy.get(commonlocators.toastMsg).contains("Play success"); */ - cy.get(widgetsPage.autoPlay).click(); + cy.get(widgetsPage.autoPlay).click({ force: true }); cy.wait("@updateLayout").should( "have.nested.property", "response.body.responseMeta.status", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicInput/autocomplete_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicInput/autocomplete_spec.js index 6f2e0eebfd..ecc7c5d252 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicInput/autocomplete_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicInput/autocomplete_spec.js @@ -6,29 +6,23 @@ const dynamicInputLocators = require("../../../../locators/DynamicInput.json"); const apiwidget = require("../../../../locators/apiWidgetslocator.json"); describe("Dynamic input autocomplete", () => { - beforeEach(() => { + before(() => { cy.addDsl(dsl); }); it("opens autocomplete for bindings", () => { cy.openPropertyPane("buttonwidget"); cy.get(dynamicInputLocators.input) .first() - .focus() - .type("{ctrl}{shift}{downarrow}") - .then(($cm) => { - if ($cm.val() !== "") { - cy.get(dynamicInputLocators.input) - .first() - .clear({ - force: true, - }); - } - + .click({ force: true }) + .type("{uparrow}", { parseSpecialCharSequences: true }) + .type("{ctrl}{shift}{downarrow}", { parseSpecialCharSequences: true }) + .type("{backspace}", { parseSpecialCharSequences: true }) + .then(() => { cy.get(dynamicInputLocators.input) .first() + .click({ force: true }) .type("{{", { - force: true, - parseSpecialCharSequences: false, + parseSpecialCharSequences: true, }); // Tests if autocomplete will open @@ -36,15 +30,17 @@ describe("Dynamic input autocomplete", () => { // Tests if data tree entities are sorted cy.get(`${dynamicInputLocators.hints} li`) - .first() - .should("have.text", "Aditya"); + .eq(1) + .should("have.text", "Aditya.backgroundColor"); // Tests if "No suggestions" message will pop if you type any garbage cy.get(dynamicInputLocators.input) .first() - .type("garbage", { - force: true, - parseSpecialCharSequences: false, + .click({ force: true }) + .type("{uparrow}", { parseSpecialCharSequences: true }) + .type("{ctrl}{shift}{downarrow}", { parseSpecialCharSequences: true }) + .type("{{ garbage", { + parseSpecialCharSequences: true, }) .then(() => { cy.get(".CodeMirror-Tern-tooltip").should( @@ -53,21 +49,16 @@ describe("Dynamic input autocomplete", () => { ); }); }); + cy.evaluateErrorMessage("ReferenceError: garbage is not defined"); }); it("opens current value popup", () => { - // Test on widgets pane - cy.openPropertyPane("buttonwidget"); - cy.get(dynamicInputLocators.input) - .first() - .focus(); - cy.assertEvaluatedValuePopup("string"); // Test on api pane cy.NavigateToAPI_Panel(); cy.get(apiwidget.createapi).click({ force: true }); cy.wait("@createNewApi"); - cy.xpath(apiwidget.headerValue) - .first() - .focus(); + cy.get(apiwidget.headerValue).within(() => { + cy.get("textarea").click({ force: true }); + }); cy.assertEvaluatedValuePopup("string"); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js index a2d387bd31..ae64c14c37 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js @@ -1,5 +1,6 @@ const testdata = require("../../../../fixtures/testdata.json"); const apiwidget = require("../../../../locators/apiWidgetslocator.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); const explorer = require("../../../../locators/explorerlocators.json"); const commonlocators = require("../../../../locators/commonlocators.json"); const formWidgetsPage = require("../../../../locators/FormWidgets.json"); @@ -15,7 +16,8 @@ describe("Entity explorer Drag and Drop widgets testcases", function() { cy.get(commonlocators.entityExplorersearch) .clear() .type("form"); - cy.dragAndDropToCanvas("formwidget", { x: 300, y: -300 }); + cy.dragAndDropToCanvas("formwidget", { x: 300, y: 80 }); + cy.get(formWidgetsPage.formD).click(); /** * @param{Text} Random Text * @param{FormWidget}Mouseover @@ -29,10 +31,13 @@ describe("Entity explorer Drag and Drop widgets testcases", function() { /** * @param{Text} Random Colour */ - cy.testCodeMirror(this.data.colour); + cy.get(widgetsPage.backgroundcolorPicker) + .first() + .click({ force: true }); + cy.xpath(widgetsPage.greenColor).click(); cy.get(formWidgetsPage.formD) .should("have.css", "background-color") - .and("eq", this.data.rgbValue); + .and("eq", "rgb(3, 179, 101)"); /** * @param{toggleButton Css} Assert to be checked */ diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Query_Datasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Query_Datasource_spec.js index f094bf74d7..01ddf0a9b5 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Query_Datasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Query_Datasource_spec.js @@ -5,6 +5,7 @@ const datasource = require("../../../../locators/DatasourcesEditor.json"); const apiwidget = require("../../../../locators/apiWidgetslocator.json"); const commonlocators = require("../../../../locators/commonlocators.json"); const explorer = require("../../../../locators/explorerlocators.json"); +const pages = require("../../../../locators/Pages.json"); const pageid = "MyPage"; let datasourceName; @@ -59,7 +60,7 @@ describe("Entity explorer tests related to query and datasource", function() { /* eslint-disable */ cy.wait(2000); - cy.go("back"); + cy.NavigateToQueryEditor(); cy.contains(".t--datasource-name", datasourceName) .find(queryLocators.editDatasourceButton) @@ -101,10 +102,11 @@ describe("Entity explorer tests related to query and datasource", function() { .find(explorer.collapse) .click(); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(3); + expect($lis).to.have.length(4); expect($lis.eq(0)).to.contain("{{Query1.isLoading}}"); expect($lis.eq(1)).to.contain("{{Query1.data}}"); - expect($lis.eq(2)).to.contain("{{Query1.run()}}"); + expect($lis.eq(2)).to.contain("{{Query1.responseMeta}}"); + expect($lis.eq(3)).to.contain("{{Query1.run()}}"); }); cy.Createpage(pageid); cy.GlobalSearchEntity("Query1"); @@ -120,7 +122,12 @@ describe("Entity explorer tests related to query and datasource", function() { cy.runQuery(); cy.deleteQuery(); - + cy.get(commonlocators.entityExplorersearch).clear({ force: true }); + cy.wait(500); + cy.NavigateToQueryEditor(); + cy.get(pages.integrationActiveTab) + .should("be.visible") + .click({ force: true }); cy.contains(".t--datasource-name", datasourceName) .find(".t--edit-datasource") .click(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Tab_rename_Delete_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Tab_rename_Delete_spec.js index 178635df67..1b0841806c 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Tab_rename_Delete_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Tab_rename_Delete_spec.js @@ -31,10 +31,9 @@ describe("Tab widget test", function() { cy.RenameEntity(tabname); cy.validateMessage(tabname); cy.deleteEntity(); - cy.get(commonlocators.entityExplorersearch).should("be.visible"); cy.get(commonlocators.entityExplorersearch) - .clear() - .type("Tab 2"); + .clear({ force: true }) + .type("Tab 2", { force: true }); cy.get( commonlocators.entitySearchResult.concat("Tab 2").concat("')"), ).should("not.exist"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js index c6627d91bf..2a4774093a 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js @@ -28,7 +28,8 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() { cy.wait(500); cy.get(commonlocators.toastBody) .first() - .contains("Copied"); + .contains("Copied") + .click(); cy.get("body").type(`{${modifierKey}}v`, { force: true }); cy.wait("@updateLayout").should( "have.nested.property", @@ -45,11 +46,11 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() { cy.get(commonlocators.toastAction) .contains("UNDO") .click({ force: true }); - cy.GlobalSearchEntity("Form1"); + cy.GlobalSearchEntity("FormTestCopy"); cy.get(apiwidget.propertyList).then(function($lis) { expect($lis).to.have.length(2); - expect($lis.eq(0)).to.contain("{{Form1.isVisible}}"); - expect($lis.eq(1)).to.contain("{{Form1.data}}"); + expect($lis.eq(0)).to.contain("{{FormTestCopy.isVisible}}"); + expect($lis.eq(1)).to.contain("{{FormTestCopy.data}}"); }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js index f7cecc69a4..3309634c54 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js @@ -5,6 +5,8 @@ const homePage = require("../../../../locators/HomePage.json"); const pages = require("../../../../locators/Pages.json"); const publishPage = require("../../../../locators/publishWidgetspage.json"); const modalWidgetPage = require("../../../../locators/ModalWidget.json"); +const datasource = require("../../../../locators/DatasourcesEditor.json"); +const queryLocators = require("../../../../locators/QueryEditor.json"); describe("Button Widget Functionality", function() { before(() => { @@ -15,6 +17,20 @@ describe("Button Widget Functionality", function() { cy.openPropertyPane("buttonwidget"); }); + it("Button-Style Validation", function() { + //Changing the style of the button from the property pane and verify it's color. + // Change to Secondary button sytle + cy.changeButtonStyle(2, "rgba(0, 0, 0, 0)", "rgba(0, 0, 0, 0)"); + cy.get(publishPage.backToEditor).click({ force: true }); + // Change to Danger button sytle + cy.openPropertyPane("buttonwidget"); + cy.changeButtonStyle(3, "rgb(179, 3, 56)", "rgb(139, 2, 43)"); + cy.get(publishPage.backToEditor).click({ force: true }); + // Change to Primary button sytle + cy.openPropertyPane("buttonwidget"); + cy.changeButtonStyle(1, "rgb(3, 179, 101)", "rgb(2, 139, 78)"); + }); + it("Button-Name validation", function() { //changing the Button Name cy.widgetText( @@ -71,6 +87,35 @@ describe("Button Widget Functionality", function() { ); }); + it("Toggle JS - Button-Disable Validation", function() { + //Check the disabled checkbox by using JS widget and Validate + cy.get(widgetsPage.toggleDisable).click({ force: true }); + cy.EditWidgetPropertiesUsingJS(widgetsPage.inputToggleDisable, "true"); + cy.validateDisableWidget( + widgetsPage.buttonWidget, + commonlocators.disabledField, + ); + cy.PublishtheApp(); + cy.validateDisableWidget( + publishPage.buttonWidget, + commonlocators.disabledField, + ); + }); + + it("Toggle JS - Button-Enable Validation", function() { + //Uncheck the disabled checkbox and validate + cy.EditWidgetPropertiesUsingJS(widgetsPage.inputToggleDisable, "false"); + cy.validateEnableWidget( + widgetsPage.buttonWidget, + commonlocators.disabledField, + ); + cy.PublishtheApp(); + cy.validateEnableWidget( + publishPage.buttonWidget, + commonlocators.disabledField, + ); + }); + it("Button-Unckeck Visible field Validation", function() { //Uncheck the disabled checkbox and validate cy.UncheckWidgetProperties(commonlocators.visibleCheckbox); @@ -85,6 +130,21 @@ describe("Button Widget Functionality", function() { cy.get(publishPage.buttonWidget).should("be.visible"); }); + it("Toggle JS - Button-Unckeck Visible field Validation", function() { + //Uncheck the disabled checkbox using JS and validate + cy.get(widgetsPage.toggleVisible).click({ force: true }); + cy.EditWidgetPropertiesUsingJS(widgetsPage.inputToggleVisible, "false"); + cy.PublishtheApp(); + cy.get(publishPage.buttonWidget).should("not.exist"); + }); + + it("Toggle JS - Button-Check Visible field Validation", function() { + //Check the disabled checkbox using JS and Validate + cy.EditWidgetPropertiesUsingJS(widgetsPage.inputToggleVisible, "true"); + cy.PublishtheApp(); + cy.get(publishPage.buttonWidget).should("be.visible"); + }); + it("Button-AlertModal Validation", function() { //creating the Alert Modal and verify Modal name cy.createModal("Alert Modal", this.data.AlertModalName); @@ -107,6 +167,124 @@ describe("Button Widget Functionality", function() { ); }); + it("Button-CallAnApi Validation", function() { + //creating an api and calling it from the onClickAction of the button widget. + // Creating the api + cy.NavigateToAPI_Panel(); + cy.CreateAPI("buttonApi"); + cy.log("Creation of buttonApi Action successful"); + cy.enterDatasourceAndPath(this.data.paginationUrl, "users?page=4&size=3"); + cy.SaveAndRunAPI(); + + // Going to HomePage where the button widget is located and opeing it's property pane. + cy.get(widgetsPage.NavHomePage).click({ force: true }); + cy.reload(); + cy.openPropertyPane("buttonwidget"); + + // Adding the api in the onClickAction of the button widget. + cy.addAPIFromLightningMenu("buttonApi"); + // Filling the messages for success/failure in the onClickAction of the button widget. + cy.onClickActions("Success", "Error"); + + cy.PublishtheApp(); + + // Clicking the button to verify the success message + cy.get(publishPage.buttonWidget).click(); + cy.get(widgetsPage.apiCallToast).should("have.text", "Success"); + }); + + it("Button-Call-Query Validation", function() { + //creating a query and calling it from the onClickAction of the button widget. + // Creating a mock query + // cy.CreateMockQuery("Query1"); + let postgresDatasourceName; + + cy.startRoutesForDatasource(); + cy.NavigateToDatasourceEditor(); + cy.get(datasource.PostgreSQL).click(); + cy.generateUUID().then((uid) => { + postgresDatasourceName = uid; + + cy.get(".t--edit-datasource-name").click(); + cy.get(".t--edit-datasource-name input") + .clear() + .type(postgresDatasourceName, { force: true }) + .should("have.value", postgresDatasourceName) + .blur(); + }); + cy.wait("@saveDatasource").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.fillPostgresDatasourceForm(); + cy.saveDatasource(); + + cy.CreateMockQuery("Query1"); + + // Going to HomePage where the button widget is located and opeing it's property pane. + cy.get(widgetsPage.NavHomePage).click({ force: true }); + cy.reload(); + cy.openPropertyPane("buttonwidget"); + + // Adding the query in the onClickAction of the button widget. + cy.addQueryFromLightningMenu("Query1"); + // Filling the messages for success/failure in the onClickAction of the button widget. + cy.onClickActions("Success", "Error"); + + cy.PublishtheApp(); + + // Clicking the button to verify the success message + cy.get(publishPage.buttonWidget).click(); + cy.get(widgetsPage.apiCallToast).should("have.text", "Success"); + }); + + it("Toggle JS - Button-CallAnApi Validation", function() { + //creating an api and calling it from the onClickAction of the button widget. + // calling the existing api + cy.get(widgetsPage.toggleOnClick).click({ force: true }); + cy.testJsontext( + "onclick", + "{{buttonApi.run(() => showAlert('Success','success'), () => showAlert('Error','error'))}}", + ); + + cy.PublishtheApp(); + + // Clicking the button to verify the success message + cy.get(publishPage.buttonWidget).click(); + cy.get(widgetsPage.apiCallToast).should("have.text", "Success"); + }); + + it("Toggle JS - Button-Call-Query Validation", function() { + //creating a query and calling it from the onClickAction of the button widget. + // Creating a mock query + cy.testJsontext( + "onclick", + "{{Query1.run(() => showAlert('Success','success'), () => showAlert('Error','error'))}}", + ); + + cy.PublishtheApp(); + + // Clicking the button to verify the success message + cy.get(publishPage.buttonWidget).click(); + cy.get(widgetsPage.apiCallToast).should("have.text", "Success"); + }); + + it("Button-Copy Verification", function() { + const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl"; + //Copy button and verify all properties + cy.copyWidget("buttonwidget", widgetsPage.buttonWidget); + + cy.PublishtheApp(); + }); + + it("Button-Delete Verification", function() { + // Delete the button widget + cy.deleteWidget(widgetsPage.buttonWidget); + cy.PublishtheApp(); + cy.get(widgetsPage.buttonWidget).should("not.exist"); + }); + afterEach(() => { cy.get(publishPage.backToEditor).click({ force: true }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckBox_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckBox_spec.js index e987207b66..f3503292c7 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckBox_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/CheckBox_spec.js @@ -81,7 +81,7 @@ describe("Checkbox Widget Functionality", function() { cy.get(publish.checkboxWidget).click(); cy.get(widgetsPage.formButtonWidget) .contains("Submit") - .should("have.attr", "disabled"); + .should("have.class", "bp3-disabled"); cy.get(publish.checkboxWidget).click(); cy.get(widgetsPage.formButtonWidget) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_2_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_2_spec.js index 1f8676f85c..0208539c48 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_2_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_2_spec.js @@ -74,13 +74,44 @@ describe("DatePicker Widget Property pane tests with js bindings", function() { cy.get(".t--property-control-defaultdate .bp3-input").type("2020-02-01"); cy.closePropertyPane(); cy.openPropertyPane("datepickerwidget2"); - cy.get(formWidgetsPage.toggleJsMinDate).click(); + cy.get(formWidgetsPage.toggleJsMinDate).click({ force: true }); cy.get(".t--property-control-mindate .bp3-input").type("2020-01-01"); - cy.get(formWidgetsPage.toggleJsMaxDate).click(); + cy.get(formWidgetsPage.toggleJsMaxDate).click({ force: true }); cy.get(".t--property-control-maxdate .bp3-input").type("2020-02-10"); cy.closePropertyPane(); }); + it("Datepicker input value changes to work with selected date formats", function() { + cy.openPropertyPane("datepickerwidget2"); + cy.get(".t--property-control-mindate .bp3-input") + .clear() + .type("2021-01-01"); + cy.closePropertyPane(); + cy.openPropertyPane("datepickerwidget2"); + cy.get(".t--property-control-maxdate .bp3-input") + .clear() + .type("2021-10-10"); + cy.closePropertyPane(); + cy.openPropertyPane("datepickerwidget2"); + cy.get(".t--property-control-defaultdate .bp3-input").clear(); + cy.selectDateFormat("DD/MM/YYYY HH:mm"); + cy.get(formWidgetsPage.toggleJsDefaultDate).click(); + cy.testJsontext( + "defaultdate", + '{{moment("04/05/2021 05:25", "DD/MM/YYYY HH:mm").toISOString()}}', + ); + cy.get(".t--draggable-datepickerwidget2 .bp3-input") + .clear({ + force: true, + }) + .type("04/05/2021 06:25"); + cy.selectDateFormat("LLL"); + cy.wait("@updateLayout"); + cy.get(".t--draggable-textwidget .bp3-ui-text") + .first() + .should("have.text", "May 4, 2021 6:25 AM"); + }); + it("Datepicker default date validation with js binding", function() { cy.PublishtheApp(); // eslint-disable-next-line cypress/no-unnecessary-waiting diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js index 50bef3f6c9..70cef20567 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js @@ -3,6 +3,7 @@ const formWidgetsPage = require("../../../../locators/FormWidgets.json"); const publish = require("../../../../locators/publishWidgetspage.json"); const dsl = require("../../../../fixtures/formdsl.json"); const pages = require("../../../../locators/Pages.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); describe("Form Widget Functionality", function() { before(() => { @@ -23,10 +24,13 @@ describe("Form Widget Functionality", function() { /** * @param{Text} Random Colour */ - cy.testCodeMirror(this.data.colour); + cy.get(widgetsPage.backgroundcolorPicker) + .first() + .click({ force: true }); + cy.xpath(widgetsPage.greenColor).click(); cy.get(formWidgetsPage.formD) .should("have.css", "background-color") - .and("eq", this.data.rgbValue); + .and("eq", "rgb(3, 179, 101)"); /** * @param{toggleButton Css} Assert to be checked */ @@ -40,7 +44,7 @@ describe("Form Widget Functionality", function() { it("Form Widget Functionality To Verify The Colour", function() { cy.get(formWidgetsPage.formD) .should("have.css", "background-color") - .and("eq", this.data.rgbValue); + .and("eq", "rgb(3, 179, 101)"); }); it("Form Widget Functionality To Unchecked Visible Widget", function() { cy.get(publish.backToEditor).click(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Radio_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Radio_spec.js index cf11fdd239..32b67709b9 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Radio_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Radio_spec.js @@ -49,7 +49,6 @@ describe("Radio Widget Functionality", function() { cy.get(formWidgetsPage.radioOnSelectionChangeDropdown) .get(commonlocators.dropdownSelectButton) .click({ force: true }) - .type(this.data.command) .type("2"); cy.PublishtheApp(); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RichTextEditor_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RichTextEditor_spec.js index 6f186627f0..3377ba5c83 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RichTextEditor_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/RichTextEditor_spec.js @@ -29,17 +29,6 @@ describe("RichTextEditor Widget Functionality", function() { "h1", "This is a Heading", ); - - // validate after reload - cy.reload(true); - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(2000); - cy.validateHTMLText( - formWidgetsPage.richTextEditorWidget, - "h1", - "This is a Heading", - ); - cy.PublishtheApp(); cy.validateHTMLText( publishPage.richTextEditorWidget, diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GlobalSearch/GlobalSearch_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GlobalSearch/GlobalSearch_spec.js index 3f60f9e819..19986c87ad 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GlobalSearch/GlobalSearch_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GlobalSearch/GlobalSearch_spec.js @@ -42,8 +42,8 @@ describe("GlobalSearch", function() { .its("store") .invoke("getState") .then((state) => { - const { selectedWidget } = state.ui.widgetDragResize; - expect(selectedWidget).to.be.equal(table.widgetId); + const { lastSelectedWidget } = state.ui.widgetDragResize; + expect(lastSelectedWidget).to.be.equal(table.widgetId); }); }); @@ -119,5 +119,6 @@ describe("GlobalSearch", function() { expect(loc.pathname).includes(expectedPage.pageId); }); }); + cy.NavigateToHome(); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Tab_new_scenario_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Tab_new_scenario_spec.js new file mode 100644 index 0000000000..4dddb88105 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Tab_new_scenario_spec.js @@ -0,0 +1,40 @@ +const commonlocators = require("../../../../locators/commonlocators.json"); +const Layoutpage = require("../../../../locators/Layout.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); +const publish = require("../../../../locators/publishWidgetspage.json"); +const dsl = require("../../../../fixtures/tabsWithWidgetDsl.json"); +const pages = require("../../../../locators/Pages.json"); +const modalWidgetPage = require("../../../../locators/ModalWidget.json"); + +describe("Tab widget test", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Tab Widget Functionality Test with Modal on change of selected tab", function() { + cy.openPropertyPane("tabswidget"); + cy.widgetText("tab", Layoutpage.tabWidget, Layoutpage.tabInput); + cy.AddActionWithModal(); + cy.get(".t--widget-buttonwidget:contains('Confirm')").click({ + force: true, + }); + }); + + it("Publih the app and validate the widgets displayed under each tab", function() { + cy.PublishtheApp(); + cy.get(publish.buttonWidget).should("be.visible"); + cy.get(publish.textWidget).should("be.visible"); + cy.get(publish.datePickerNew).should("be.visible"); + cy.get(publish.tab) + .contains("Tab 2") + .click({ force: true }); + cy.get(publish.checkboxWidget).should("be.visible"); + cy.get(publish.radioWidget).should("be.visible"); + cy.get(publish.buttonWidget) + .contains("Confirm") + .click({ force: true }); + }); +}); + +afterEach(() => { + // put your clean up code if any +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Tab_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Tab_spec.js index c777b51360..6261817622 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Tab_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/LayoutWidgets/Tab_spec.js @@ -60,9 +60,9 @@ describe("Tab widget test", function() { .contains(this.data.tabName) .click({ force: true }) .should("be.selected"); + cy.get(publish.backToEditor).click(); }); it("Tab Widget Functionality To Unchecked Visible Widget", function() { - cy.get(publish.backToEditor).click(); cy.openPropertyPane("tabswidget"); cy.togglebarDisable(commonlocators.visibleCheckbox); cy.PublishtheApp(); @@ -74,8 +74,71 @@ describe("Tab widget test", function() { cy.togglebar(commonlocators.visibleCheckbox); cy.PublishtheApp(); cy.get(publish.tabWidget).should("be.visible"); + cy.get(publish.backToEditor).click(); }); + + it("Tab Widget Functionality To Check tab invisiblity", function() { + cy.openPropertyPane("tabswidget"); + cy.get(Layoutpage.tabEdit) + .eq(1) + .click({ force: true }); + cy.get(Layoutpage.tabVisibility) + .first() + .click({ force: true }); + cy.get(Layoutpage.tabWidget) + .contains("Tab 1") + .should("not.exist"); + cy.PublishtheApp(); + cy.get(publish.tabWidget) + .contains("Tab 1") + .should("not.exist"); + cy.get(publish.backToEditor).click(); + }); + + it("Tab Widget Functionality To Check tab visibility", function() { + cy.openPropertyPane("tabswidget"); + cy.get(Layoutpage.tabEdit) + .eq(1) + .click({ force: true }); + cy.get(Layoutpage.tabVisibility) + .first() + .click({ force: true }); + cy.get(Layoutpage.tabWidget) + .contains("Tab 1") + .should("be.visible"); + cy.PublishtheApp(); + cy.get(publish.tabWidget) + .contains("Tab 1") + .should("be.visible"); + cy.get(publish.backToEditor).click(); + }); + /* Test to be revisted as the undo action is inconsistent in automation + it("Tab Widget Functionality To Check undo action after delete", function() { + cy.openPropertyPane("tabswidget"); + cy.get(Layoutpage.tabDelete) + .eq(1) + .click({ force: true }); + cy.wait(2000); + cy.wait("@updateLayout"); + cy.get(publish.tabWidget) + .contains("Tab 1") + .should("not.exist"); + cy.get(".undo-section:contains('UNDO')") + .should("be.visible") + .click({ force: true }); + cy.wait(2000); + cy.wait("@updateLayout"); + cy.get(Layoutpage.tabWidget) + .contains("Tab 1") + .should("be.visible"); + cy.PublishtheApp(); + cy.get(publish.tabWidget) + .contains("Tab 1") + .should("be.visible"); + }); + */ }); + afterEach(() => { // put your clean up code if any }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Onboarding/Onboarding_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Onboarding/Onboarding_spec.js index d8712160d2..8b4b21a142 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Onboarding/Onboarding_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Onboarding/Onboarding_spec.js @@ -2,6 +2,9 @@ // const explorer = require("../../../../locators/explorerlocators.json"); const homePage = require("../../../../locators/HomePage.json"); const commonlocators = require("../../../../locators/commonlocators.json"); +const pages = require("../../../../locators/Pages.json"); +const datasourceEditor = require("../../../../locators/DatasourcesEditor.json"); +const datasource = require("../../../../locators/DatasourcesEditor.json"); describe("Onboarding", function() { it("Onboarding flow", function() { @@ -50,7 +53,7 @@ describe("Onboarding", function() { // Add widget cy.get(".t--add-widget").click(); - cy.dragAndDropToCanvas("tablewidget", { x: 30, y: -30 }); + cy.dragAndDropToCanvas("tablewidget", { x: 360, y: 40 }); // wait for animation duration // eslint-disable-next-line cypress/no-unnecessary-waiting diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/LeaveOrganizationTest_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/LeaveOrganizationTest_spec.js new file mode 100644 index 0000000000..d720788c33 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/LeaveOrganizationTest_spec.js @@ -0,0 +1,62 @@ +/// + +const homePage = require("../../../../locators/HomePage.json"); + +describe("Leave organization test spec", function() { + let newOrgId; + let newOrganizationName; + + it("leave organization menu is visible validation", function() { + cy.visit("/applications"); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + newOrgId = interception.response.body.data.name; + cy.visit("/applications"); + cy.openOrgOptionsPopup(newOrganizationName); + cy.contains("Leave Organization"); + }); + }); + + it("Only admin user can not leave organization validation", function() { + cy.visit("/applications"); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + newOrgId = interception.response.body.data.name; + cy.visit("/applications"); + cy.openOrgOptionsPopup(newOrganizationName); + cy.contains("Leave Organization").click(); + cy.contains("Are you sure").click(); + cy.wait("@leaveOrgApiCall").then((httpResponse) => { + expect(httpResponse.status).to.equal(400); + }); + cy.contains(newOrganizationName); + }); + }); + + it("Non admin users can only access leave organization popup menu validation", function() { + cy.visit("/applications"); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + newOrgId = interception.response.body.data.name; + cy.visit("/applications"); + cy.inviteUserForOrg( + newOrganizationName, + Cypress.env("TESTUSERNAME1"), + homePage.viewerRole, + ); + cy.LogOut(); + + cy.LogintoApp(Cypress.env("TESTUSERNAME1"), Cypress.env("TESTPASSWORD1")); + cy.visit("/applications"); + cy.openOrgOptionsPopup(newOrganizationName); + cy.get(homePage.orgNamePopoverContent) + .find("a") + .should("have.length", 1) + .first() + .contains("Leave Organization"); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js new file mode 100644 index 0000000000..620b05e0aa --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js @@ -0,0 +1,39 @@ +const homePage = require("../../../../locators/HomePage.json"); + +describe("Organization Import Application", function() { + let orgid; + let newOrganizationName; + const fixtureDummyAppPath = "application-file.json"; + it("Can Import Application", function() { + cy.NavigateToHome(); + cy.generateUUID().then((uid) => { + orgid = uid; + localStorage.setItem("OrgName", orgid); + cy.createOrg(); + cy.wait("@createOrg").then((createOrgInterception) => { + newOrganizationName = createOrgInterception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + cy.get(homePage.orgImportAppOption).click({ force: true }); + + cy.get(homePage.orgImportAppModal).should("be.visible"); + cy.xpath(homePage.uploadLogo).attachFile(fixtureDummyAppPath); + + cy.get(homePage.orgImportAppButton).click({ force: true }); + cy.wait("@importNewApplication").then((interception) => { + let appId = interception.response.body.data.id; + let defaultPage = interception.response.body.data.pages.find( + (eachPage) => !!eachPage.isDefault, + ); + cy.get(homePage.toastMessage).should( + "contain", + "Application imported successfully", + ); + cy.url().should( + "include", + `/applications/${appId}/pages/${defaultPage.id}/edit`, + ); + }); + }); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgSettings_validation_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgSettings_validation_spec.js index e606c8376c..5c5ee05712 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgSettings_validation_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgSettings_validation_spec.js @@ -4,6 +4,7 @@ const homePage = require("../../../../locators/HomePage.json"); describe("Org Settings validation spec", function() { let orgid; + let newOrganizationName; it("create org with long name should use ellipsis validation", function() { cy.NavigateToHome(); @@ -13,15 +14,21 @@ describe("Org Settings validation spec", function() { uid; localStorage.setItem("OrgName", orgid); // create org with long name - cy.createOrg(orgid); - cy.navigateToOrgSettings(orgid); - // checking parent's() since the child() inherits css from it - cy.get(homePage.orgHeaderName) - .parent() - .then((elem) => { - assert.isBelow(elem[0].offsetWidth, elem[0].scrollWidth); - }) - .should("have.css", "text-overflow", "ellipsis"); + cy.createOrg(); + // stub the response and + // find app name + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + cy.navigateToOrgSettings(orgid); + // checking parent's() since the child() inherits css from it + cy.get(homePage.orgHeaderName) + .parent() + .then((elem) => { + assert.isBelow(elem[0].offsetWidth, elem[0].scrollWidth); + }) + .should("have.css", "text-overflow", "ellipsis"); + }); }); cy.LogOut(); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgUserIconTest_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgUserIconTest_spec.js index e4b0fba386..35ee38be2c 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgUserIconTest_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgUserIconTest_spec.js @@ -4,21 +4,26 @@ const homePage = require("../../../../locators/HomePage.json"); describe("Check if org has user icons on homepage", function() { let orgid; + let newOrganizationName; it("create org and check if user icons exists in that org on homepage", function() { cy.NavigateToHome(); cy.generateUUID().then((uid) => { orgid = uid; localStorage.setItem("OrgName", orgid); - cy.createOrg(orgid); - cy.get(homePage.orgList.concat(orgid).concat(")")) - .scrollIntoView() - .should("be.visible") - .within(() => { - cy.get(homePage.shareUserIcons) - .first() - .should("be.visible"); - }); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + cy.get(homePage.orgList.concat(orgid).concat(")")) + .scrollIntoView() + .should("be.visible") + .within(() => { + cy.get(homePage.shareUserIcons) + .first() + .should("be.visible"); + }); + }); }); cy.LogOut(); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/Orgname_validation_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/Orgname_validation_spec.js index cefe2baab0..8ab08a7c6a 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/Orgname_validation_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/Orgname_validation_spec.js @@ -3,28 +3,40 @@ const homePage = require("../../../../locators/HomePage.json"); describe("Org name validation spec", function() { + let orgid; + let newOrganizationName; it("create org with leading space validation", function() { cy.NavigateToHome(); - cy.get(homePage.createOrg) - .should("be.visible") - .first() - .click({ force: true }); - cy.xpath(homePage.inputOrgName) - .should("be.visible") - .type(" "); - cy.get(homePage.submit).should("be.disabled"); - cy.xpath(homePage.cancelBtn).click(); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.NavigateToHome(); + cy.contains(newOrganizationName).click({ force: true }); + cy.get(homePage.renameOrgInput) + .should("be.visible") + .type(" "); + cy.get(".error-message").should("be.visible"); + }); + }); + it("creates org and checks that orgname is editable", function() { + cy.createOrg(); + cy.generateUUID().then((uid) => { + orgid = + "kadjhfkjadsjkfakjdscajdsnckjadsnckadsjcnanakdjsnckjdscnakjdscnnadjkncakjdsnckjadsnckajsdfkjadshfkjsdhfjkasdhfkjasdhfjkasdhjfasdjkfhjhdsfjhdsfjhadasdfasdfadsasdf" + + uid; + // create org with long name + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + }); + }); }); - it("create org with special characters validation", function() { - cy.get(homePage.createOrg) - .should("be.visible") - .first() - .click({ force: true }); - cy.xpath(homePage.inputOrgName) - .should("be.visible") - .type("Test & Org"); - cy.get(homePage.submit).should("be.enabled"); - cy.xpath(homePage.cancelBtn).click(); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, "Test & Org"); + }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Pages_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Pages_spec.js index 95133ddc2e..fb7a3de75c 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Pages_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Pages_spec.js @@ -23,7 +23,9 @@ describe("Pages", function() { cy.get(".bp3-icon-caret-right ~ .t--entity-name:contains(Page1)").click({ multiple: true, }); - cy.get(".bp3-icon-caret-right ~ .t--entity-name:contains(APIs)").click({ + cy.get( + ".bp3-icon-caret-right ~ .t--entity-name:contains(Datasources)", + ).click({ multiple: true, }); cy.get(`.t--entity-name:contains(${apiName})`).should("have.length", 2); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ProductUpdates/ProductUpdates_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ProductUpdates/ProductUpdates_spec.js index db433c36c7..ef124c354f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ProductUpdates/ProductUpdates_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ProductUpdates/ProductUpdates_spec.js @@ -10,10 +10,10 @@ describe("Check for product updates button and modal", function() { .its("store") .invoke("getState") .then((state) => { - const { releaseItems, newReleasesCount } = state.ui.releases; + const { newReleasesCount, releaseItems } = state.ui.releases; if (Array.isArray(releaseItems) && releaseItems.length > 0) { cy.get("[data-cy=t--product-updates-btn]") - .contains(newReleasesCount) + .contains("What's New?") .click({ force: true }); //eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(500); // modal transition diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PropertyPane/PropertyPaneCTA_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PropertyPane/PropertyPaneCTA_spec.js new file mode 100644 index 0000000000..6bdd252016 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PropertyPane/PropertyPaneCTA_spec.js @@ -0,0 +1,21 @@ +const dsl = require("../../../../fixtures/TextTabledsl.json"); + +describe("Property pane CTA to add an action", function() { + before(() => { + cy.addDsl(dsl); + }); + + it("Check if CTA is shown when there is no action", function() { + cy.openPropertyPane("tablewidget"); + + cy.get(".t--propertypane-connect-cta").should("be.visible"); + }); + + it("Check if CTA does not exist when there is an action", function() { + cy.NavigateToAPI_Panel(); + + cy.CreateAPI("FirstAPI"); + cy.SearchEntityandOpen("Table1"); + cy.get(".t--propertypane-connect-cta").should("not.exist"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/QueryPaneTests/Datasourcedocs_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/QueryPaneTests/Datasourcedocs_spec.js index 85badafaa2..8834919119 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/QueryPaneTests/Datasourcedocs_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/QueryPaneTests/Datasourcedocs_spec.js @@ -1,6 +1,7 @@ const datasource = require("../../../../locators/DatasourcesEditor.json"); const queryLocators = require("../../../../locators/QueryEditor.json"); const commonlocators = require("../../../../locators/commonlocators.json"); +const queryEditor = require("../../../../locators/QueryEditor.json"); describe("Check datasource doc links", function() { let postgresDatasourceName; @@ -44,7 +45,8 @@ describe("Check datasource doc links", function() { }); it("Delete the query and datasources", function() { - cy.get(queryLocators.deleteQuery).click(); + cy.get(queryEditor.queryMoreAction).click(); + cy.get(queryEditor.deleteUsingContext).click(); cy.wait("@deleteAction").should( "have.nested.property", "response.body.responseMeta.status", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/QueryPaneTests/SwitchDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/QueryPaneTests/SwitchDatasource_spec.js index 7e4e07ef32..cdb81f2401 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/QueryPaneTests/SwitchDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/QueryPaneTests/SwitchDatasource_spec.js @@ -1,5 +1,6 @@ const datasource = require("../../../../locators/DatasourcesEditor.json"); const queryLocators = require("../../../../locators/QueryEditor.json"); +const queryEditor = require("../../../../locators/QueryEditor.json"); describe("Switch datasource", function() { let postgresDatasourceName; @@ -92,7 +93,8 @@ describe("Switch datasource", function() { }); it("Delete the query and datasources", function() { - cy.get(queryLocators.deleteQuery).click(); + cy.get(queryEditor.queryMoreAction).click(); + cy.get(queryEditor.deleteUsingContext).click(); cy.wait("@deleteAction").should( "have.nested.property", "response.body.responseMeta.status", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/WidgetSelection/WidgetSelection_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/WidgetSelection/WidgetSelection_spec.js new file mode 100644 index 0000000000..54ff3d797d --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/WidgetSelection/WidgetSelection_spec.js @@ -0,0 +1,29 @@ +const dsl = require("../../../../fixtures/widgetSelection.json"); + +describe("Widget Selection", function() { + before(() => { + cy.addDsl(dsl); + }); + + it("Multi Select widgets using cmd + click", function() { + cy.get(`#${dsl.dsl.children[0].widgetId}`).click({ + ctrlKey: true, + }); + cy.get(`#${dsl.dsl.children[1].widgetId}`).click({ + ctrlKey: true, + }); + cy.get(`.t--widget-propertypane-toggle`).should("have.length", 2); + cy.get(`#${dsl.dsl.children[2].widgetId}`).click({ + ctrlKey: true, + }); + cy.get(`.t--widget-propertypane-toggle`).should("have.length", 3); + cy.get(`#${dsl.dsl.children[0].widgetId}`).click({ + ctrlKey: true, + }); + cy.get(`.t--widget-propertypane-toggle`) + .not(`[style *= "background: rgb(255, 224, 210);"]`) // Excluding focused widgets. + .should("have.length", 2); + + cy.get(`.t--multi-selection-box`).should("have.length", 1); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/LayoutWidgets/List_spec.js b/app/client/cypress/integration/Smoke_TestSuite/LayoutWidgets/List_spec.js index 8221072b87..063471e6e5 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/LayoutWidgets/List_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/LayoutWidgets/List_spec.js @@ -4,7 +4,7 @@ const dsl = require("../../../fixtures/listdsl.json"); const publishPage = require("../../../locators/publishWidgetspage.json"); describe("Container Widget Functionality", function() { - const items = JSON.parse(dsl.dsl.children[0].items); + const items = JSON.parse(dsl.dsl.children[0].listData); before(() => { cy.addDsl(dsl); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiFlow/CurlImportFlow_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiFlow/CurlImportFlow_spec.js index 9ea0d1bad9..d33f1f2480 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiFlow/CurlImportFlow_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiFlow/CurlImportFlow_spec.js @@ -1,10 +1,14 @@ const ApiEditor = require("../../../../locators/ApiEditor.json"); const apiwidget = require("../../../../locators/apiWidgetslocator.json"); +const pages = require("../../../../locators/Pages.json"); describe("Test curl import flow", function() { it("Test curl import flow Run and Delete", function() { localStorage.setItem("ApiPaneV2", "ApiPaneV2"); cy.NavigateToApiEditor(); + cy.get(pages.integrationCreateNew) + .should("be.visible") + .click({ force: true }); cy.get(ApiEditor.curlImage).click({ force: true }); cy.get("textarea").type("curl -X GET https://mock-api.appsmith.com/users"); cy.importCurl(); @@ -27,7 +31,5 @@ describe("Test curl import flow", function() { cy.get("@deleteAction").then((response) => { cy.expect(response.response.body.responseMeta.success).to.eq(true); }); - cy.get(ApiEditor.ApiHomePage).should("be.visible"); - cy.get(ApiEditor.formActionButtons).should("not.exist"); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiPaneTests/API_All_Verb_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiPaneTests/API_All_Verb_spec.js index 5411c7745f..cb090494a1 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiPaneTests/API_All_Verb_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiPaneTests/API_All_Verb_spec.js @@ -14,11 +14,8 @@ describe("API Panel Test Functionality", function() { }); it("PUT Action test API fetaure", function() { - cy.log("Login Successful"); cy.NavigateToAPI_Panel(); - cy.log("Navigation to API Panel screen successful"); cy.CreateAPI("FirstAPI"); - cy.log("Creation of FirstAPI Action successful"); cy.SelectAction(testdata.putAction); cy.EnterSourceDetailsWithbody( testdata.baseUrl, diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiPaneTests/API_CurlPOSTImport_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiPaneTests/API_CurlPOSTImport_spec.js index b23b1b8fdb..0437fef97e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiPaneTests/API_CurlPOSTImport_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ApiPaneTests/API_CurlPOSTImport_spec.js @@ -1,10 +1,14 @@ const ApiEditor = require("../../../../locators/ApiEditor.json"); const apiwidget = require("../../../../locators/apiWidgetslocator.json"); +const pages = require("../../../../locators/Pages.json"); describe("Test curl import flow", function() { - it("Test curl import flow for POST action", function() { + it("Test curl import flow for POST action with JSON body", function() { localStorage.setItem("ApiPaneV2", "ApiPaneV2"); cy.NavigateToApiEditor(); + cy.get(pages.integrationCreateNew) + .should("be.visible") + .click({ force: true }); cy.get(ApiEditor.curlImage).click({ force: true }); cy.get("textarea").type( 'curl -d \'{"name":"morpheus","job":"leader"}\' -H Content-Type:application/json -X POST https://mock-api.appsmith.com/echo/post', @@ -26,4 +30,36 @@ describe("Test curl import flow", function() { }); }); }); + + it("Test curl import flow for POST action with multipart form data", function() { + localStorage.setItem("ApiPaneV2", "ApiPaneV2"); + cy.NavigateToApiEditor(); + cy.get(pages.integrationCreateNew) + .should("be.visible") + .click({ force: true }); + cy.get(ApiEditor.curlImage).click({ force: true }); + cy.get("textarea").type( + "curl --request POST http://httpbin.org/post -F 'randomKey=randomValue' --form 'randomKey2=\"randomValue2\"'", + { + force: true, + parseSpecialCharSequences: false, + }, + ); + cy.importCurl(); + cy.RunAPI(); + cy.ResponseStatusCheck("200 OK"); + cy.log("Ran the API successfully"); + cy.get("@postExecute").then((response) => { + cy.log(response.response.body); + cy.expect(response.response.body.responseMeta.success).to.eq(true); + // Asserting if the form key value are returned in the response + cy.expect(response.response.body.data.body.form.randomKey).to.eq( + "randomValue", + ); + // Asserting the content type header set in curl import is multipart/form-data + cy.expect( + response.response.body.data.body.headers["Content-Type"], + ).contains("multipart/form-data;boundary"); + }); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/DatasourceBasicProfile_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/DatasourceBasicProfile_spec.js new file mode 100644 index 0000000000..aac6a3abef --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/DatasourceBasicProfile_spec.js @@ -0,0 +1,26 @@ +/* + * Commenting this spec as its flaky + * + * */ +// const testdata = require("../../../../fixtures/testdata.json"); +// describe("Create a rest datasource", function() { +// beforeEach(() => { +// cy.startRoutesForDatasource(); +// }); + +// it("Create a rest datasource", function() { +// cy.NavigateToAPI_Panel(); +// cy.CreateAPI("Testapi"); +// cy.enterDatasource(testdata.basicURl); +// cy.get(".t--store-as-datasource").click(); +// cy.addBasicProfileDetails("test", "test@123"); +// cy.saveDatasource(); +// cy.contains(".datasource-highlight", "envyenksqii9nf3.m.pipedream.net"); +// cy.SaveAndRunAPI(); +// cy.wait(2000); +// var encodedStringBtoA = btoa("test:test@123"); +// cy.log(encodedStringBtoA); +// cy.ResponseStatusCheck(testdata.successStatusCode); +// cy.ResponseTextCheck("Basic ".concat(encodedStringBtoA)); +// }); +// }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MongoDataSourceStub_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MongoDataSourceStub_spec.js index 2ce7ec8e3b..1b63e64319 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MongoDataSourceStub_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MongoDataSourceStub_spec.js @@ -23,6 +23,7 @@ describe("Create, test, save then delete a mongo datasource", function() { "response.body.responseMeta.status", 200, ); + /* cy.NavigateToQueryEditor(); cy.get("@createDatasource").then((httpResponse) => { @@ -39,7 +40,8 @@ describe("Create, test, save then delete a mongo datasource", function() { "response.body.responseMeta.status", 200, ); - + cy.xpath('//div[contains(text(),"Form Input")]').click({ force: true }); + cy.xpath('//div[contains(text(),"Raw Input")]').click({ force: true }); cy.get(queryLocators.templateMenu).click(); cy.get(".CodeMirror textarea") .first() @@ -94,4 +96,5 @@ describe("Create, test, save then delete a mongo datasource", function() { ); }); */ + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/PostgresDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/PostgresDatasource_spec.js index dabd0e202a..9c0da595b9 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/PostgresDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/PostgresDatasource_spec.js @@ -1,5 +1,6 @@ const datasource = require("../../../../locators/DatasourcesEditor.json"); const queryEditor = require("../../../../locators/QueryEditor.json"); +const datasourceEditor = require("../../../../locators/DatasourcesEditor.json"); let datasourceName; @@ -21,14 +22,18 @@ describe("Postgres datasource test cases", function() { it("Create a new query from the datasource editor", function() { cy.saveDatasource(); - cy.get(datasource.createQuerty).click(); + // cy.get(datasource.createQuerty).click(); + cy.get(`${datasourceEditor.datasourceCard} ${datasource.createQuerty}`) + .last() + .click(); cy.wait("@createNewApi").should( "have.nested.property", "response.body.responseMeta.status", 201, ); - cy.get(queryEditor.deleteQuery).click(); + cy.get(queryEditor.queryMoreAction).click(); + cy.get(queryEditor.deleteUsingContext).click(); cy.wait("@deleteAction").should( "have.nested.property", "response.body.responseMeta.status", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_API_Pane_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_API_Pane_spec.js index c14b38effc..0e60dda003 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_API_Pane_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_API_Pane_spec.js @@ -27,10 +27,11 @@ describe("Entity explorer API pane related testcases", function() { .find(explorer.collapse) .click(); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(3); + expect($lis).to.have.length(4); expect($lis.eq(0)).to.contain("{{FirstAPI.isLoading}}"); expect($lis.eq(1)).to.contain("{{FirstAPI.data}}"); - expect($lis.eq(2)).to.contain("{{FirstAPI.run()}}"); + expect($lis.eq(2)).to.contain("{{FirstAPI.responseMeta}}"); + expect($lis.eq(3)).to.contain("{{FirstAPI.run()}}"); }); cy.get(apiwidget.actionlist) .contains(testdata.Get) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_CopyQuery_RenameDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_CopyQuery_RenameDatasource_spec.js index 952c0cdaaf..82a484dede 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_CopyQuery_RenameDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_CopyQuery_RenameDatasource_spec.js @@ -54,10 +54,11 @@ describe("Entity explorer tests related to copy query", function() { .find(explorer.collapse) .click(); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(3); + expect($lis).to.have.length(4); expect($lis.eq(0)).to.contain("{{Query1.isLoading}}"); expect($lis.eq(1)).to.contain("{{Query1.data}}"); - expect($lis.eq(2)).to.contain("{{Query1.run()}}"); + expect($lis.eq(2)).to.contain("{{Query1.responseMeta}}"); + expect($lis.eq(3)).to.contain("{{Query1.run()}}"); }); }); }); @@ -71,21 +72,22 @@ describe("Entity explorer tests related to copy query", function() { .invoke("show") .click({ force: true }); cy.copyEntityToPage(pageid); - cy.SearchEntityandOpen("Query1Copy"); + cy.SearchEntityandOpen("Query1"); cy.runQuery(); - cy.get(`.t--entity.action:contains(Query1Copy)`) + cy.get(`.t--entity.action:contains(Query1)`).should("have.length", 2); + cy.get(`.t--entity.action:contains(Query1)`) .find(explorer.collapse) - .click(); + .click({ multiple: true }); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(3); - expect($lis.eq(0)).to.contain("{{Query1Copy.isLoading}}"); - expect($lis.eq(1)).to.contain("{{Query1Copy.data}}"); - expect($lis.eq(2)).to.contain("{{Query1Copy.run()}}"); + expect($lis.eq(0)).to.contain("{{Query1.isLoading}}"); + expect($lis.eq(1)).to.contain("{{Query1.data}}"); + expect($lis.eq(2)).to.contain("{{Query1.responseMeta}}"); + expect($lis.eq(3)).to.contain("{{Query1.run()}}"); }); }); it("Delete query and rename datasource in explorer", function() { - cy.get(commonlocators.entityExplorersearch).clear(); + cy.get(commonlocators.entityExplorersearch).clear({ force: true }); cy.NavigateToDatasourceEditor(); cy.GlobalSearchEntity(`${datasourceName}`); cy.get(`.t--entity-name:contains(${datasourceName})`) @@ -98,7 +100,6 @@ describe("Entity explorer tests related to copy query", function() { cy.log("sliced id :" + updatedName); cy.EditEntityNameByDoubleClick(datasourceName, updatedName); cy.SearchEntityandOpen(updatedName); - cy.testSaveDatasource(); cy.hoverAndClick(); cy.get(apiwidget.delete).click({ force: true }); //This is check to make sure if a datasource is active 409 @@ -109,7 +110,7 @@ describe("Entity explorer tests related to copy query", function() { ); }); - cy.SearchEntityandOpen("Query1Copy"); + cy.SearchEntityandOpen("Query1"); cy.deleteQuery(); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js index ff72c2c1c5..ed2cfb49e6 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js @@ -63,7 +63,8 @@ describe("Entity explorer datasource structure", function() { 201, ); - cy.get(queryEditor.deleteQuery).click(); + cy.get(queryEditor.queryMoreAction).click(); + cy.get(queryEditor.deleteUsingContext).click(); cy.wait("@deleteAction").should( "have.nested.property", "response.body.responseMeta.status", @@ -72,14 +73,15 @@ describe("Entity explorer datasource structure", function() { cy.GlobalSearchEntity("MyQuery"); cy.get(`.t--entity-name:contains(MyQuery)`).click(); - cy.get(queryEditor.deleteQuery).click(); + cy.get(queryEditor.queryMoreAction).click(); + cy.get(queryEditor.deleteUsingContext).click(); cy.wait("@deleteAction").should( "have.nested.property", "response.body.responseMeta.status", 200, ); - cy.get(commonlocators.entityExplorersearch).clear(); + cy.get(commonlocators.entityExplorersearch).clear({ force: true }); cy.deleteDatasource(datasourceName); }); @@ -102,7 +104,7 @@ describe("Entity explorer datasource structure", function() { 200, ); - cy.get(commonlocators.entityExplorersearch).clear(); + cy.get(commonlocators.entityExplorersearch).clear({ force: true }); const tableName = Math.random() .toString(36) @@ -151,14 +153,15 @@ describe("Entity explorer datasource structure", function() { 200, ); - cy.get(queryEditor.deleteQuery).click(); + cy.get(queryEditor.queryMoreAction).click(); + cy.get(queryEditor.deleteUsingContext).click(); cy.wait("@deleteAction").should( "have.nested.property", "response.body.responseMeta.status", 200, ); - cy.get(commonlocators.entityExplorersearch).clear(); + cy.get(commonlocators.entityExplorersearch).clear({ force: true }); cy.deleteDatasource(datasourceName); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateAppWithSameNameInOrg_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateAppWithSameNameInOrg_spec.js index c6f78dbbad..32a444c4f0 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateAppWithSameNameInOrg_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateAppWithSameNameInOrg_spec.js @@ -3,6 +3,7 @@ describe("Create org and a new app / delete and recreate app", function() { let orgid; let appid; + let newOrganizationName; it("create app within an org and delete and re-create another app with same name", function() { cy.NavigateToHome(); @@ -10,7 +11,11 @@ describe("Create org and a new app / delete and recreate app", function() { orgid = uid; appid = uid; localStorage.setItem("OrgName", orgid); - cy.createOrg(orgid); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + }); cy.CreateAppForOrg(orgid, appid); cy.DeleteAppByApi(); cy.NavigateToHome(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateOrgTests_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateOrgTests_spec.js index 4740cd7378..d50dc6924b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateOrgTests_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateOrgTests_spec.js @@ -5,6 +5,7 @@ const homePage = require("../../../../locators/HomePage.json"); describe("Create new org and share with a user", function() { let orgid; let appid; + let newOrganizationName; it("create org and then share with a user from UI", function() { cy.NavigateToHome(); @@ -12,7 +13,11 @@ describe("Create new org and share with a user", function() { orgid = uid; appid = uid; localStorage.setItem("OrgName", orgid); - cy.createOrg(orgid); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + }); cy.CheckShareIcon(orgid, 1); cy.inviteUserForOrg( orgid, @@ -51,13 +56,7 @@ describe("Create new org and share with a user", function() { }); it("login as Org owner and update the invited user role to developer", function() { - cy.LoginFromAPI(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); - cy.visit("/applications"); - cy.wait("@applications").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); + cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); cy.get(homePage.searchInput).type(appid); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(2000); @@ -90,13 +89,7 @@ describe("Create new org and share with a user", function() { }); it("login as Org owner and update the invited user role to administrator", function() { - cy.LoginFromAPI(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); - cy.visit("/applications"); - cy.wait("@applications").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); + cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); cy.get(homePage.searchInput).type(appid); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(2000); @@ -124,13 +117,7 @@ describe("Create new org and share with a user", function() { }); it("login as Org owner and delete App ", function() { - cy.LoginFromAPI(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); - cy.visit("/applications"); - cy.wait("@applications").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); + cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); cy.get(homePage.searchInput).type(appid); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(2000); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateSameAppInDiffOrg_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateSameAppInDiffOrg_spec.js index 3de68cf260..318d304bc3 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateSameAppInDiffOrg_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateSameAppInDiffOrg_spec.js @@ -3,6 +3,7 @@ describe("Create app same name in different org", function() { let orgid; let appid; + let newOrganizationName; it("create app within a new org", function() { cy.NavigateToHome(); @@ -10,10 +11,16 @@ describe("Create app same name in different org", function() { orgid = uid; appid = uid; localStorage.setItem("OrgName", orgid); - cy.createOrg(orgid); - cy.CreateAppForOrg(orgid, appid); - cy.NavigateToHome(); - cy.LogOut(); + cy.createOrg(); + // stub the response and + // find app name + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + cy.CreateAppForOrg(orgid, appid); + cy.NavigateToHome(); + cy.LogOut(); + }); }); }); @@ -25,9 +32,13 @@ describe("Create app same name in different org", function() { "response.body.responseMeta.status", 200, ); - cy.reload(); const newOrgName = orgid + "1"; - cy.createOrg(newOrgName); - cy.CreateAppForOrg(newOrgName, appid); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + console.log("createOrganization response: ", interception); + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, newOrgName); + cy.CreateAppForOrg(newOrgName, appid); + }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/ShareAppTests_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/ShareAppTests_spec.js index 478a3b5522..d791df203e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/ShareAppTests_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/ShareAppTests_spec.js @@ -7,6 +7,7 @@ describe("Create new org and share with a user", function() { let orgid; let appid; let currentUrl; + let newOrganizationName; it("create org and then share with a user from Application share option within application", function() { cy.NavigateToHome(); @@ -14,7 +15,11 @@ describe("Create new org and share with a user", function() { orgid = uid; appid = uid; localStorage.setItem("OrgName", orgid); - cy.createOrg(orgid); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + }); cy.CreateAppForOrg(orgid, appid); cy.wait("@getPagesForCreateApp").should( "have.nested.property", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/UpdateOrgTests_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/UpdateOrgTests_spec.js index fc37647369..f8a63868d0 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/UpdateOrgTests_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/UpdateOrgTests_spec.js @@ -2,26 +2,24 @@ const homePage = require("../../../../locators/HomePage.json"); describe("Update Organization", function() { let orgid; + let newOrganizationName; it("Open the org general settings and update org name. The update should reflect in the org. It should also reflect in the org names on the left side and the org dropdown. ", function() { cy.NavigateToHome(); cy.generateUUID().then((uid) => { orgid = uid; localStorage.setItem("OrgName", orgid); - cy.createOrg(orgid); - cy.get(homePage.orgList.concat(orgid).concat(")")) - .scrollIntoView() - .should("be.visible") - .within(() => { - cy.get(".t--org-name") - .first() - .click(); - }); - cy.get(homePage.orgSettingOption).click(); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + cy.get(homePage.orgSettingOption).click({ force: true }); + }); }); cy.generateUUID().then((uid) => { orgid = uid; localStorage.setItem("OrgName", orgid); + cy.get(homePage.orgNameInput).click({ force: true }); cy.get(homePage.orgNameInput).clear(); cy.get(homePage.orgNameInput).type(orgid); // eslint-disable-next-line cypress/no-unnecessary-waiting @@ -37,15 +35,12 @@ describe("Update Organization", function() { }); it("Open the org general settings and update org email. The update should reflect in the org.", function() { - cy.get(homePage.orgList.concat(orgid).concat(")")) - .scrollIntoView() - .should("be.visible") - .within(() => { - cy.get(".t--org-name") - .first() - .click(); - }); - cy.get(homePage.orgSettingOption).click(); + cy.createOrg(); + cy.wait("@createOrg").then((interception) => { + newOrganizationName = interception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + cy.get(homePage.orgSettingOption).click({ force: true }); + }); cy.get(homePage.orgEmailInput).clear(); cy.get(homePage.orgEmailInput).type(Cypress.env("TESTUSERNAME2")); cy.wait("@updateOrganization").should( diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/ConfirmRunAction_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/ConfirmRunAction_spec.js index 312c09fa0d..19977d12df 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/ConfirmRunAction_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/ConfirmRunAction_spec.js @@ -41,7 +41,8 @@ describe("Confirm run action", function() { 200, ); - cy.get(queryEditor.deleteQuery).click(); + cy.get(queryEditor.queryMoreAction).click(); + cy.get(queryEditor.deleteUsingContext).click(); cy.wait("@deleteAction").should( "have.nested.property", "response.body.responseMeta.status", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/MongoDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/MongoDatasource_spec.js index d64c91b60c..b5ce52ffb5 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/MongoDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/MongoDatasource_spec.js @@ -35,6 +35,10 @@ describe("Create a query with a mongo datasource, run, save and then delete the 200, ); + cy.xpath('//div[contains(text(),"Find Document(s)")]').click({ + force: true, + }); + cy.xpath('//div[contains(text(),"Raw")]').click({ force: true }); cy.get(queryLocators.templateMenu).click(); cy.get(".CodeMirror textarea") .first() diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/PostgreDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/PostgreDatasource_spec.js index d291516b41..2eda8700f9 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/PostgreDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/PostgreDatasource_spec.js @@ -1,5 +1,6 @@ const queryLocators = require("../../../../locators/QueryEditor.json"); const datasource = require("../../../../locators/DatasourcesEditor.json"); +const pages = require("../../../../locators/Pages.json"); let datasourceName; @@ -39,6 +40,9 @@ describe("Create a query with a postgres datasource, run, save and then delete t }); it("Create, runs and delete another query", () => { cy.NavigateToQueryEditor(); + cy.get(pages.integrationActiveTab) + .should("be.visible") + .click({ force: true }); cy.contains(".t--datasource-name", datasourceName) .find(queryLocators.createQuery) .click(); @@ -53,6 +57,9 @@ describe("Create a query with a postgres datasource, run, save and then delete t }); it("Deletes a datasource", () => { cy.NavigateToQueryEditor(); + cy.get(pages.integrationActiveTab) + .should("be.visible") + .click({ force: true }); cy.contains(".t--datasource-name", datasourceName) .find(".t--edit-datasource") .click(); diff --git a/app/client/cypress/locators/DatasourcesEditor.json b/app/client/cypress/locators/DatasourcesEditor.json index 47aa855c89..834f6726ac 100644 --- a/app/client/cypress/locators/DatasourcesEditor.json +++ b/app/client/cypress/locators/DatasourcesEditor.json @@ -13,6 +13,8 @@ "sectionAuthentication": "[data-cy=section-Authentication]", "PostgresEntity": ".t--entity-name:contains(PostgreSQL)", "createQuerty": ".t--create-query", + "activeDatasourceList": ".t--active-datasource-list", + "datasourceCard": ".t--datasource", "editDatasource": ".t--edit-datasource", "defaultDatabaseName": "input[name='datasourceConfiguration.connection.defaultDatabaseName']", "selConnectionType": "[data-cy='datasourceConfiguration.connection.type']", @@ -35,5 +37,8 @@ "grantType": "[data-cy='authentication.grantType']", "authorizationURL":"[data-cy='authentication.authorizationUrl'] input", "authorisecode": "//div[contains(@class,'option') and text()='Authorization Code']", - "saveAndAuthorize": "button:contains('Save and Authorize')" + "saveAndAuthorize": "button:contains('Save and Authorize')", + "basic": "//div[contains(@class,'option') and text()='Basic']", + "basicUsername": "input[name='authentication.username']", + "basicPassword": "input[name='authentication.password']" } diff --git a/app/client/cypress/locators/Debugger.json b/app/client/cypress/locators/Debugger.json new file mode 100644 index 0000000000..9e5cda0e4c --- /dev/null +++ b/app/client/cypress/locators/Debugger.json @@ -0,0 +1,4 @@ +{ + "debuggerIcon": ".t--debugger", + "debuggerLogState": ".t--debugger-log-state" +} \ No newline at end of file diff --git a/app/client/cypress/locators/DynamicInput.json b/app/client/cypress/locators/DynamicInput.json index 8f34ce8219..8e5b4bc686 100644 --- a/app/client/cypress/locators/DynamicInput.json +++ b/app/client/cypress/locators/DynamicInput.json @@ -1,5 +1,5 @@ { - "input": ".CodeMirror textarea", + "input": ".CodeEditorTarget", "hints": "ul.CodeMirror-hints", "evaluatedValue": ".t--CodeEditor-evaluatedValue", "bindingPrompt": ".t--no-binding-prompt" diff --git a/app/client/cypress/locators/HomePage.json b/app/client/cypress/locators/HomePage.json index a936620af6..d6b0808e4b 100644 --- a/app/client/cypress/locators/HomePage.json +++ b/app/client/cypress/locators/HomePage.json @@ -9,6 +9,7 @@ "appMoreIcon": ".bp3-popover-wrapper.more .bp3-popover-target", "duplicateApp": "[data-cy=t--duplicate]", "forkAppFromMenu": "[data-cy=t--fork-app]", + "exportAppFromMenu": "[data-cy=t--export-app]", "forkAppOrgList": ".radio-group", "forkAppOrgButton": "[data-cy=t--fork-app-to-org-button]", "selectAction": "#Base", @@ -59,7 +60,13 @@ "applicationColorSelector": ".t--color-not-selected", "applicationBackgroundColor": ".t--application-card-background", "orgSettingOption": "[data-cy=t--org-setting]", + "orgImportAppOption": "[data-cy=t--org-import-app]", + "orgImportAppModal": ".t--import-application-modal", + "orgImportAppButton": "[data-cy=t--org-import-app-button]", + "leaveOrgConfirmModal": ".t--member-delete-confirmation-modal", + "leaveOrgConfirmButton": "[data-cy=t--org-leave-button]", "orgNameInput": "[data-cy=t--org-name-input]", + "renameOrgInput": "[data-cy=t--org-rename-input]", "orgEmailInput": "[data-cy=t--org-email-input]", "orgWebsiteInput": "[data-cy=t--org-website-input]", "orgHeaderName": ".t--organization-header", @@ -72,5 +79,7 @@ "generalTab": "//li//span[text()='General']", "membersTab": "//li//span[text()='Members']", "cancelBtn": "//span[text()='Cancel']", - "submit": "button:contains('Submit')" + "submit": "button:contains('Submit')", + "orgNamePopover": ".t--org-name", + "orgNamePopoverContent": ".bp3-popover-content" } diff --git a/app/client/cypress/locators/Layout.json b/app/client/cypress/locators/Layout.json index 7bd865494f..a9799b5de4 100644 --- a/app/client/cypress/locators/Layout.json +++ b/app/client/cypress/locators/Layout.json @@ -5,5 +5,7 @@ "tabDefault": ".t--property-control-defaulttab .CodeMirror-code", "tabButton": ".t--property-control-tabs button", "tabDelete": ".t--property-control-tabs .t--delete-tab-btn", - "tabContainer": "div[type='TABS_WIDGET']" + "tabContainer": "div[type='TABS_WIDGET']", + "tabEdit": ".t--property-control-tabs .t--edit-column-btn", + "tabVisibility": ".t--property-control-visible .bp3-control-indicator" } \ No newline at end of file diff --git a/app/client/cypress/locators/Pages.json b/app/client/cypress/locators/Pages.json index 2044baf6a8..b68297e32d 100644 --- a/app/client/cypress/locators/Pages.json +++ b/app/client/cypress/locators/Pages.json @@ -9,7 +9,9 @@ "Menuaction": ".bp3-overlay-open>.bp3-transition-container", "Delete": ":nth-child(2) > .bp3-menu-item", "apiEditorIcon": ".t--nav-link-api-editor", - "addEntityAPI": ".apis .t--entity-add-btn", + "addEntityAPI": ".datasources .t--entity-add-btn", + "integrationCreateNew": "[data-cy=t--tab-CREATE_NEW]", + "integrationActiveTab": "[data-cy=t--tab-ACTIVE]", "entityWidget": ".t--entity-name:contains('Widgets')", "entityTable": ".t--entity-name:contains('Table1')", "entityText": ".t--entity-name:contains('Text1')", diff --git a/app/client/cypress/locators/QueryEditor.json b/app/client/cypress/locators/QueryEditor.json index 28929954ba..1d736eda63 100644 --- a/app/client/cypress/locators/QueryEditor.json +++ b/app/client/cypress/locators/QueryEditor.json @@ -1,11 +1,18 @@ { + "addNewQueryBtn": ".dbqueries .t--entity-add-btn", "queryEditorIcon": ".t--nav-link-query-editor", "templateMenu": ".t--template-menu", "runQuery": ".t--run-query", "saveQuery": ".t--save-query", "deleteQuery": ".t--delete-query", + "queryMoreAction": ".t--more-action-menu", + "deleteUsingContext": ".t--apiFormDeleteBtn", "createQuery": ".t--create-query", "addQueryEntity": ".//div[contains(@class,'t--entity group queries')]//div[contains(@class,'t--entity-add-btn')]", "addDatasource": ".t--add-datasource", - "editDatasourceButton": ".t--edit-datasource" -} + "editDatasourceButton": ".t--edit-datasource", + "queryNameField": ".t--action-name-edit-field input", + "settings": "li:contains('Settings')", + "query": "li:contains('Query')", + "switch": ".t--form-control-SWITCH input" +} \ No newline at end of file diff --git a/app/client/cypress/locators/ViewWidgets.json b/app/client/cypress/locators/ViewWidgets.json index f11d43e422..d71bd9d2cc 100644 --- a/app/client/cypress/locators/ViewWidgets.json +++ b/app/client/cypress/locators/ViewWidgets.json @@ -17,7 +17,7 @@ "chartInnerText": ".t--property-control-title", "inputChartValue": ".t--property-control-chartseries .CodeMirror textarea", "chartButton": ".t--property-control-chartseries button", - "rectangleChart": ".t--draggable-chartwidget g rect", + "rectangleChart": ".t--draggable-chartwidget g rect", "xlabel": ".t--property-control-x-axislabel .CodeMirror-code", "ylabel": ".t--property-control-y-axislabel .CodeMirror-code", "mapInner": ".t--draggable-mapwidget span.t--widget-name", @@ -27,7 +27,8 @@ "createMarker": ".t--property-control-createnewmarker [type='checkbox']", "zoomLevel": ".t--property-control-zoomlevel svg", "sourceImage": ".t--property-control-image .CodeMirror-code", - "defaultImage": ".t--property-control-defaultimage .CodeMirror-code", + "defaultImage": ".t--property-control-defaultimage .CodeMirror textarea", "Chartlabel": ".t--draggable-chartwidget g:nth-child(5) text", + "PieChartLabel": ".t--draggable-chartwidget g g:nth-child(1) g text", "pickMyLocation": ".t--draggable-mapwidget div[title='Pick My Location']" } diff --git a/app/client/cypress/locators/Widgets.json b/app/client/cypress/locators/Widgets.json index 3e67eecebb..38b87d7465 100644 --- a/app/client/cypress/locators/Widgets.json +++ b/app/client/cypress/locators/Widgets.json @@ -1,16 +1,23 @@ { + "NavHomePage": "[data-icon='home']", "containerWidget": ".t--draggable-containerwidget", "inputWidget": ".t--draggable-inputwidget", "togglebutton": "input[type='checkbox']", "inputPropsDataType": ".t--property-control-datatype", "inputdatatypeplaceholder": ".t--property-control-placeholder", "buttonWidget": ".t--draggable-buttonwidget", + "buttonStyleDropdown": ".t--property-control-buttonstyle [name='downArrow']", + "buttonBackground": ".sc-ecQjpJ > div > .bp3-button", + "copyWidget": ".t--copy-widget svg", + "removeWidget": ".t--delete-widget svg", + "propertypaneText": ".t--propertypane .bp3-panel-stack-view", "formButtonWidget": ".t--widget-formbuttonwidget", "textWidget": ".t--draggable-textwidget", "tableWidget": ".t--draggable-tablewidget", "tableOnRowSelected": ".t--property-control-onrowselected", "dropdownSelectButton": ".t--open-dropdown-Select", "buttonOnClick": ".t--property-control-onclick .bp3-popover-target", + "buttonCreateApi": "a.t--create-api-btn", "Scrollbutton": ".t--property-control-scrollcontents input", "label": ".t--draggable-inputwidget label", "inputval": ".t--draggable-inputwidget span.t--widget-name", @@ -38,6 +45,8 @@ "textInputval": ".t--draggable-textwidget span.t--widget-name", "textCenterAlign": ".t--property-control-textalign .t--icon-tab-CENTER", "ColumnAction": ".t--property-control-rowbutton button", + "SearchTextChangeAction": ".t--property-control-onsearchtextchanged button", + "tableSearchTextChangeSelected": ".t--property-control-onsearchtextchanged", "videoWidget": ".t--draggable-videowidget", "autoPlay": ".t--property-control-autoplay > .bp3-control > .bp3-control-indicator", "defaultOption": ".t--property-control-defaultoption .CodeMirror-code", @@ -66,7 +75,7 @@ "verticalBottom": ".t--icon-tab-BOTTOM", "textColor": ".t--property-control-textcolor input", "backgroundcolorPicker": ".t--property-control-backgroundcolor input", - "greenColor": "//div[@color='rgb(3, 179, 101)']", + "greenColor": "//div[@color='#03b365']", "toggleJsColor": ".t--property-control-textcolor .t--js-toggle", "backgroundColor": ".t--property-control-cellbackground input", "toggleJsBcgColor": ".t--property-control-cellbackground .t--js-toggle", @@ -75,6 +84,13 @@ "textSize": ".t--property-control-textsize .bp3-popover-target", "toggleTextSize": ".t--property-control-textsize .t--js-toggle", "toggleVerticalAlig": ".t--property-control-verticalalignment .t--js-toggle", + "toggleVisible": ".t--property-control-visible .t--js-toggle", + "inputToggleVisible": "div.t--property-control-visible div.CodeMirror-lines", + "toggleDisable": ".t--property-control-disabled .t--js-toggle", + "inputToggleDisable": "div.t--property-control-disabled div.CodeMirror-lines", + "toggleOnClick": ".t--property-control-onclick .t--js-toggle", + "toggleChartType": ".t--property-control-charttype .t--js-toggle", + "inputToggleOnClick": ".t--property-control-onclick div.CodeMirror-lines", "tableBtn": ".t--draggable-tablewidget .bp3-button", "toastAction": ".Toastify__toast-container--top-right .t--toast-action", "toastActionText": ".Toastify__toast-container--top-right .t--toast-action span", @@ -82,9 +98,13 @@ "selectWidget": ".t--open-dropdown-Select-Widget", "switchWidget": ".t--widget-switchwidget", "toastMsg": ".t--toast-action span", + "deleteToast": "div[class = 'undo-section'] span[type='h6']", + "apiCallToast": "div.t--toast-action span[type='p1']", "datepickerInput": ".t--draggable-datepickerwidget2 input", "selectToday": ".DayPicker-Day--today", "switchWidgetActive": ".t--switch-widget-active", "switchWidgetInactive": ".t--switch-widget-inactive", - "switchWidgetLoading": ".t--switch-widget-loading" + "switchWidgetLoading": ".t--switch-widget-loading", + "chartPlotGroup": "g.raphael-group-63-plot-group", + "toggleEnableMultirowselection": ".t--property-control-enablemultirowselection .bp3-control-indicator" } diff --git a/app/client/cypress/locators/apiWidgetslocator.json b/app/client/cypress/locators/apiWidgetslocator.json index 2727f72efe..c49c628b0b 100644 --- a/app/client/cypress/locators/apiWidgetslocator.json +++ b/app/client/cypress/locators/apiWidgetslocator.json @@ -12,12 +12,11 @@ "path": ".t--path >div textarea", "editResourceUrl": ".t--dataSourceField", "autoSuggest": "//div[contains(@id,'react-select')]", - "headerKey": "(//div[contains(@class,'t--actionConfiguration.headers[0].key.0')]//textarea)[2]", - "headerValue": "(//div[contains(@class,'t--actionConfiguration.headers[0].value.0')]//textarea)[2]", - "postDefaultContentTypeHeaderKey": "(//div[contains(@class,'t--actionConfiguration.headers[2].key.2')]//textarea)[2]", - "queryKey": "(//div[contains(@class,'t--actionConfiguration.queryParameters[0].key.0')]//textarea)[2]", - "queryValue": "(//div[contains(@class,'t--actionConfiguration.queryParameters[0].value.0')]//textarea)[2]", - "formEncoded": "(//div[contains(@class,'t--actionConfiguration.bodyFormData[0].key.0')]//textarea)[2]", + "headerKey": ".t--actionConfiguration\\.headers\\[0\\]\\.key\\.0", + "headerValue": ".t--actionConfiguration\\.headers\\[0\\]\\.value\\.0", + "queryKey": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.key\\.0", + "queryValue": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.value\\.0", + "formEncoded": ".t--actionConfiguration\\.bodyFormData\\[0\\]\\.key\\.0", "responseStatus": "//div[@id='root']", "responseText": ".CodeMirror-line > [role='presentation']", "createApiOnSideBar": "button:contains('Create new API')", @@ -52,5 +51,8 @@ "actionlist": ".action div div", "settings": "li:contains('Settings')", "onPageLoad": "[data-cy=executeOnLoad]", - "renameEntity": ".single-select >div:contains('Edit Name')" + "renameEntity": ".single-select >div:contains('Edit Name')", + "paramsTab": "//li//span[text()='Params']", + "paramKey": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.key\\.0", + "paramValue": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.value\\.0" } diff --git a/app/client/cypress/locators/commonlocators.json b/app/client/cypress/locators/commonlocators.json index 7248cf2091..968b5ad27a 100644 --- a/app/client/cypress/locators/commonlocators.json +++ b/app/client/cypress/locators/commonlocators.json @@ -13,9 +13,9 @@ "optionchangetextDropdown": " .t--property-control-onoptionchange .CodeMirror-code", "optionchangetextDatePicker": " .t--property-control-ondateselected .CodeMirror-code", "optionchangetextCheckbox": ".t--property-control-oncheckchange .CodeMirror-code", - "optionchangetextSwitch": ".t--property-control-onchange .CodeMirror-code", + "optionchangetextSwitch": ".t--property-control-onchange .CodeMirror textarea", "optionchangetextInput": ".t--property-control-ontextchanged .CodeMirror-code", - "optionchangeRadioselect": ".t--property-control-onselectionchange .CodeMirror-code", + "optionchangeRadioselect": ".t--property-control-onselectionchange .CodeMirror textarea", "optionalignment": ".t--property-control-alignment .bp3-popover-target", "onMarkerclick": ".t--property-control-onmarkerclick .CodeMirror-code", "success": "div[type='success'] span", diff --git a/app/client/cypress/locators/explorerlocators.json b/app/client/cypress/locators/explorerlocators.json index 47b927f915..42e0c2bb9b 100644 --- a/app/client/cypress/locators/explorerlocators.json +++ b/app/client/cypress/locators/explorerlocators.json @@ -3,7 +3,7 @@ "NoQueryMsg": "p:contains('No DB Queries yet.')", "NoWidgetsMsg": "p:contains('No widgets yet.')", "AddPage": ".pages .t--entity-add-btn", - "addEntityAPI": ".apis .t--entity-add-btn", + "addEntityAPI": ".datasources .t--entity-add-btn", "entityWidget": ".t--entity-name:contains('Widgets')", "entityTable": ".t--entity-name:contains('Table1')", "entityText": ".t--entity-name:contains('Text1')", @@ -13,7 +13,7 @@ "editName": ".single-select >div:contains('Edit Name')", "deletePage": ".single-select >div:contains('Delete')", "refreshStructure": ".single-select >div:contains('Refresh')", - "entityQuery": ".t--entity-name:contains('Queries')", + "entityQuery": ".t--entity-name:contains('Datasources')", "datasourceStructure": ".t--entity.datasourceStructure", "datasourceColumn": ".t--datasource-column", "templateMenuIcon": ".t--template-menu-trigger", @@ -25,6 +25,6 @@ "addWidget":".widgets .t--entity-add-btn", "dropHere":".appsmith_widget_0", "closeWidgets":".t--close-widgets-sidebar", - "addDBQueryEntity": ".dbqueries .t--entity-add-btn", + "addDBQueryEntity": ".datasources .t--entity-add-btn", "editEntity": ".t--entity-name input" } \ No newline at end of file diff --git a/app/client/cypress/locators/publishWidgetspage.json b/app/client/cypress/locators/publishWidgetspage.json index b4a75d5dad..8e91ef0bfe 100644 --- a/app/client/cypress/locators/publishWidgetspage.json +++ b/app/client/cypress/locators/publishWidgetspage.json @@ -22,7 +22,7 @@ "pickMyLocation": ".t--widget-mapwidget div[title='Pick My Location']", "rectChart": ".t--widget-chartwidget g rect", "chartLab": ".t--widget-chartwidget g:nth-child(5) text", - "searchInput": "input", + "searchInput": ".t--search-input", "downloadBtn": ".t--table-download-btn", "filterBtn": ".t--table-filter-toggle-btn", "attributeDropdown": ".t--table-filter-columns-dropdown", @@ -31,11 +31,13 @@ "inputValue": ".t--table-filter-value-input", "canvas": ".canvas", "removeFilter": ".t--table-filter-remove-btn", - "compactMode": ".t--table-compact-mode-toggle-btn", - "compactOpt": ".t--table-compact-mode-option", + "rowHeight": ".t--table-compact-mode-toggle-btn", + "rowHeightOpt": ".t--table-compact-mode-option", "visibilityMode": ".t--table-column-visibility-toggle-btn", "visibilityOpt": ".option-title", "containerWidget": ".t--widget-containerwidget", "pageInfo": ".bp3-heading", - "inputGrp": ".bp3-input-group input" + "inputGrp": ".bp3-input-group input", + "datePickerNew": ".t--widget-datepickerwidget2", + "tab": ".t--tab-Tab" } \ No newline at end of file diff --git a/app/client/cypress/manual_TestSuite/GoogleSheet_API_Spec.js b/app/client/cypress/manual_TestSuite/GoogleSheet_API_Spec.js new file mode 100644 index 0000000000..3a43e1db1d --- /dev/null +++ b/app/client/cypress/manual_TestSuite/GoogleSheet_API_Spec.js @@ -0,0 +1,90 @@ +const queryLocators = require("../../../locators/QueryEditor.json"); +const queryEditor = require("../../../locators/QueryEditor.json"); +let datasourceName; + +describe("Test Ideas for GooglSheet API", function() { + it("Add a Datasource", function() { + //Add the datasource to Email Id + //Ensure different Email Id can be associate to different Email Id + //Ensure Datasource has two action "Read Only" and "Read" + //Ensure user can Save the Datasource + //Click on "Add API" + }); + + it("List API", function() { + //Select the Method "List Sheet" + //Ensure response : URL and Name of the sheet + //Ensure "Add Widget" is displayed to user + //Ensure click on Add widget the data gets populated on the widget + }); + + it("Fetch a Sheet", function() { + //Select the Method "Fetch Sheet" + //Ensure response :id,name,createdTime,modifiedTime,permissions + }); + + it("Create New Sheet", function() { + //Ensure the response is appropriate + //Ensure to select the method + //Ensure to select a new name to the spreadsheet + //Provide an existing name to the new sheet and observe sheet should be created + //Send a empty response body and ensure an empty sheet is created in the email Id selected + //Enter sheet name as special charaters,alphanumeric keys and number and observe + //Send the response body with defined value and ensure the data is added + }); + + it("Insert a sheet or Update a sheet", function() { + //Ensure to select the method + //Ensure to add the spreadsheet URL + //Ensure add the sheet name in which it needs to be inserted + //Ensure add the Table Heading Row Index + //Ensure to add appropriate raw object + //Ensure to submit one row empty each time and account the error + //Ensure to make changes in the raw object to throw error + //Add inappropriate data in each field to get appropriate error + //Add few inappropritae row name and value and check for partial insertion + //Miss an "rowIndex" field and check for and error + //Ensure to update the "headername" + }); + + it("Bulk Insert or Bulk Update", function() { + //Ensure to select the method + //Ensure to add the spreadsheet URL + //Ensure add the Table Heading Row Index + //Ensure to add appropriate raw object + //Add inappropriate data in each field to get appropriate error + //Add few inappropritae row name and value and check for partial insertion + //Try inserting without Row Index - Row index is not manadate of insert + //Add Different Sheet Url from the same doc and correct sheet name ensure teh correct gets updated + //Add doc URL different and name of sheet that doesnt exsit in the doc and ensure the error is displayed + }); + + it("Delete a Row", function() { + //Ensure to select the method + //Ensure to add the spreadsheet URL + //Ensure to add the sheet name + //Ensure add the Table Heading Row Index + //Ensure to add the valid index + //Enter index as 0 and check if the table header is not deleted + //Enter index as on and check the first data value gets deleted + }); + + it("Delete a Sheet", function() { + //Ensure to select the method + //Ensure to add the spreadsheet URL + //Ensure to add the sheet name + //Choose Entity has Single and try to delete the only one sheet which is available in the spreadsheet and check for the error messageEntity + //Choose Entity has Entire spreadsheet and try to deleted the sheet + }); + + it("Fetch Sheet Rows", function() { + //Ensure to select the method + //Ensure to add the spreadsheet URL + //Ensure to add the sheet name + //Enter the Table heading row index + //Choose the Query format as Query range and enter valid Range for example A2:B + //Choose the Query format as Query range and enter invalid Range and check for the error message + //Choose the Query format as Query rows and pass the valid Row limit and row offset + //Choose the Query format as Query rows and pass the invalid Row limit and row offset and check for the error message + }); +}); diff --git a/app/client/cypress/manual_TestSuite/List_Widget_Spec.js b/app/client/cypress/manual_TestSuite/List_Widget_Spec.js new file mode 100644 index 0000000000..720bd2ab87 --- /dev/null +++ b/app/client/cypress/manual_TestSuite/List_Widget_Spec.js @@ -0,0 +1,93 @@ +const dsl = require("../../../fixtures/ListWidgetDsl.json"); + +describe("List Widget test ideas ", function() { + it("List widget background colour and deploy ", function() { + // Drag and drop a List widget + // Open Property pane + // Scroll down to Styles + // Add background colour + // Add item background colour + // Ensure the colour are added appropriately + // Click on Deploy and ensure it is deployed appropriately + }); + + it("Adding large item Spacing for item card", function() { + // Drag and drop a List widget + // Open Property pane + // Scroll down to Styles + // Add large item spacing (>100) + // Ensure the cards get spaced appropriately + }); + + it("Binding an API data to list widget ", function() { + //Add an API + // Drag and drop a List widget + // Open list Property pane + // Bind the API to list widget + // Add Input widget into the list widget + // Bind the input widgte to the list widget + }); + + it("Copy Paste and Delete the List Widget ", function() { + // Drag and drop a List widget + // Click on the property pane + // Click on Copy the widget + // Paste(cmd+v) the list widget + // Click on the delete option of the Parent widget + }); + + it("Renaming the widget from Property pane and Entity explorer ", function() { + // Drag and drop a List widget + // Click on the property pane + // Click name of the widget + // Rename the widget + // Navigate to the Entity Explorer + // Click on the Widget expands + // Navigate to List widget (Double Click) + // Rename the widget + // Ensure the name of the widget is possible from both the place + }); + + it("Verify the Pagination functionlaity within List Widget", function() { + // Drag and Drop list Widget + // Click on page 2 + // Ensure list widget will be redirected to page 2 + // Click on next button + // Ensure the list widget will be redirected to page 3 + // Click on Previous button + // Ensure the list widget will be redirected to page 2 + // Mouse Hover on the next button + // Ensure the tool tip message is appropriate + // Mouse Hover on the Previous button + // Ensure the tool tip message is appropriate + }); + + it("Add new item in the list widget array object", function() { + //Drag and drop list widget + //Click to open an property pane + //Expand Genearl section + //Add the following new item + //("id": 7,"num": 007",name": Charizard",img": "http://www.serebii.net/pokemongo/pokemon/006.png") + //Ensure the new item gets added to the list widget without any error + //Check for the new page is added upon adding new items + }); + + it("Adding apt widget into the List widget", function() { + //Drag and Drop List widget + //Expand the section 1 size in the list widget + //Ensure by exapdning section inside list widget the page size gets increased + //Drag and Drop button widget inside list widget + //Ensure Button widget can be placed inside list Widget + //Drag and Drop Image widget inside list widget + //Ensure Image widget can be placed inside list widget + // Drag and drop the text widget inside the list widget + // Ensure text widget can be place inside the list widget + }); + + it("Adding unapt widget to identify the error message", function() { + //Drag and Drop List widget + //Expand the section 1 size in the list widget + //Drag and Drop widgets ie: Chart ,Date Picker radio button etc + // Ensure an understandable error message is displayed to user + }); +}); diff --git a/app/client/cypress/manual_TestSuite/Mongo_Datasource_Spec.js b/app/client/cypress/manual_TestSuite/Mongo_Datasource_Spec.js new file mode 100644 index 0000000000..71535487b0 --- /dev/null +++ b/app/client/cypress/manual_TestSuite/Mongo_Datasource_Spec.js @@ -0,0 +1,78 @@ +const queryLocators = require("../../../locators/QueryEditor.json"); +const queryEditor = require("../../../locators/QueryEditor.json"); +let datasourceName; + +describe("Test Ideas for Mongo DB Form Input", function() { + it("Insert a Document", function() { + //Ensure by choosing command as 'Insert a Document' the following fields will be displayed 'Collection Name' and 'Documents' + //Click on Command and enter command name + //Clicking on Collection name field ensure Evaluvated value popup appears and Evaluvated value should match + //Click on Document and pass the following valid query + //Clicking on Document field ensure Evaluvated value popup appears and Evaluvated value should match + //Click on run and ensure query runs successfuly + //Try passing the invalid query in the document and run and verfiy the error message + }); + + it("Find One or More Document", function() { + // Ensure by choosing command as 'Find one or more Document' the following fields will be displayed 'Collection Name' , 'Query' , 'Sort' , 'Projection' , 'Limit' and 'Skip' + //Click on collection name and enter valid collection name (In which document is inserted ealrier) + //Clicking on Collection Name field ensure Evaluvated value popup appears and Evaluvated value should match + //Click on Query field and pass {} to get all the documents inside the collection + //Pass the following value in the query field to get particular document in an collection {"_id":ObjectId("id")} + //Clicking on Query field ensure Evaluvated value popup appears and Evaluvated value should match + //click on run and ensure query runs successfully + //Compare the response with the document inserted earlier and value should match + }); + + it("Update One Document", function() { + //Ensure by choosing command as 'Update one Document' the following fields will be displayed 'Collection Name' , 'Query' , 'Sort' and 'Update' + //Click on collection name and enter valid collection name (In which document is inserted ealrier) + //Clicking on Collection Name field ensure Evaluvated value popup appears and Evaluvated value should match + //Click on Query field and pass {} to get all the documents inside the collection + //Pass the following value in the query field to get particular document in an collection {"_id":ObjectId("id")} + //Clicking on Query field ensure Evaluvated value popup appears and Evaluvated value should match + //Click on Update field and pass the following query {"set":{"status":"Inactive"}} + //Repeat 'Find one or More Document' scenario and verfiy if the update results are matching + //click on run and ensure query runs successfully + //Click on Update field and pass any invalid query and ensure query response with appropriate error message + }); + + it("Update One or More Document", function() { + //Ensure by choosing command as 'Update one or More Document' the following fields will be displayed 'Collection Name' , 'Query' and 'Update' + //Click on collection name and enter valid collection name (In which document is inserted ealrier) + //Clicking on Collection Name field ensure Evaluvated value popup appears and Evaluvated value should match + //Click on Query field and pass {} to get all the documents inside the collection + //Pass the following value in the query field to get particular document in an collection {"_id":ObjectId("id")} + //Clicking on Query field ensure Evaluvated value popup appears and Evaluvated value should match + //Click on Update field and pass the following query {"set":{"status":"Inactive"}} + //Repeat 'Find one or More Document' scenario and verify if the update results are matching + //click on run and ensure query runs successfully + //Click on Update field and pass any invalid query and ensure query response with appropriate error message + }); + + it("Delet One or More Document", function() { + //Ensure by choosing command as 'Delete one or more Document' the following fields will be displayed 'Collection Name' , 'Query' and 'limit' + //Ensure limit has two option 'Single Document' and 'All Matching document' + //Click on collection name and enter valid collection name (In which document is inserted ealrier) + //Clicking on Collection Name field ensure Evaluvated value popup appears and Evaluvated value should match + //Choose limit as single document and in the query pass the document id which needs to be deleted {"_id":ObjectId("id")} + //Clicking on Query field ensure Evaluvated value popup appears and Evaluvated value should match + //Click on run and ensure the query runs successfully + //Repeat 'Find one or More Document' scenario and verify if the specified document is deleted + //Choose limit as All Matching document and in the query pass {} to delete all the document or pass multi id's + //Click on run and ensure the query runs successfully + //Repeat 'Find one or More Document' scenario and verify if the specified document is deleted + }); + + it("Perform Distinct Operation on a Document", function() { + //Ensure by choosing command as 'Count' the following fields will be displayed 'Collection Name' , 'Query' and 'Key/Field' + //Click on collection name and enter valid collection name (In which document is inserted ealrier) + //Clicking on Collection Name field ensure Evaluvated value popup appears and Evaluvated value should match + }); + + it("Perform Aggregate Operation on a Document", function() { + //Ensure by choosing command as 'Count' the following fields will be displayed 'Collection Name' and 'Array of Pipelines' + //Click on collection name and enter valid collection name (In which document is inserted ealrier) + //Clicking on Collection Name field ensure Evaluvated value popup appears and Evaluvated value should match + }); +}); diff --git a/app/client/cypress/manual_TestSuite/Tab_Widget_Spec.js b/app/client/cypress/manual_TestSuite/Tab_Widget_Spec.js new file mode 100644 index 0000000000..6e88a6da63 --- /dev/null +++ b/app/client/cypress/manual_TestSuite/Tab_Widget_Spec.js @@ -0,0 +1,85 @@ +const dsl = require("../../../fixtures/TabWidgetDsl.json"); + +describe("Tab widget", function() { + it("Movement of tabs inside Tab widget ", function() { + // Drag and drop the Tab widget + // click on "Add a Tab" + // Add multiple Tabs + // Hold and move the Tab + // and observe if the tab are moved in the same + }); + + it(" Deletion of Tabs and adding them back with Undo", function() { + // Drag and drop the Tab widget + // click on "Add a Tab" + // Add multiple Tabs + // Click on delete option of the Tab + // ensure the tab is deleted + // Click on delete option of the Tab + // Ensure an info message is dispalyed to user + // Now click on "UNDO" + //and observe that the Tab is added back + }); + + it("Test Ideas for testing the Visible option for tabs ", function() { + // Drag and drop the Tab widget + // click on "Add a Tab" + // Click on Property pane of the tab widget + // Click on the Control Pane of the tab + // Now click on JS option + // Now add it false + // and observe the Tab is blured on edit Mode + // Now click on Deploy + // Observe the tab is not dispalyed to user + // Now come back to Edit mode + // Click on the Control Pane of the tab + // Now click on JS option + // enable the button + // Now observe the Tab must be visible and normal + }); + + it("Test Ideas for testing the Show Tabs Feature ", function() { + // Drag and drop the Tab widget + // Click on Property pane of the tab widget + // Scroll down to Show Tabs option + // Now click on JS option + // Now add it false + // and observe the Tab widget does not show any Tabs + // Now click on Deploy + // Observe the Tab widget does not show any Tabs + // Now come back to Edit mode + // Now click on JS option + // enable the button + // Now observe the Tab must be visible + }); + + it("Adding multiple widgets inside the Tab widget", function() { + // Drag and drop the Tab widget + // Ensure default 2 Tabs are dispalyed to user + // Add date picker, Text and Button into Tab1 + // Click on Tab2 and ensure it is empty + // Add image widget , radio, Check widget + // Click on Deploy + // Ensure the Tab widget with widgets are displayed to user + }); + + it("Adding action while changing the Tab ", function() { + // Drag and drop the Tab widget + // Click on Property pane of the tab widget + // Navigate to Action section + // Select "Show a modal" + // Click on "Add a Modal" + // Assign related action on the modal + // Now change the tab + // and observe the modal pop up is displayed to user + }); + + it("Binding the Tab to widget ", function() { + // Drag and drop the Tab widget + // Click on Property pane of the tab widget + // Navigate to control pane + // Convert a Visible option to JS + // Add binding to the widget + // Ensure user is dispalyed on the TAB widget + }); +}); diff --git a/app/client/cypress/manual_TestSuite/Text_Widget_Spec.js b/app/client/cypress/manual_TestSuite/Text_Widget_Spec.js new file mode 100644 index 0000000000..d180857d68 --- /dev/null +++ b/app/client/cypress/manual_TestSuite/Text_Widget_Spec.js @@ -0,0 +1,106 @@ +const homePage = require("../../../locators/Textwidget.json"); + +describe("Test Ideas to test different feature of text widget ", function() { + it("Add New Text widget along with BG and text colour ", function() { + // Navigate to application + // Drag and drop a Text Widget + // Navigate to Property Pane + // Add a text + // Scroll to BG colour and add a colour + // Next add a text colour + // Click on Deploy + }); + + it("Enable Scroll feature with text colour ", function() { + // Navigate to application + // Drag and drop a Text Widget + // Add a long text in the "Label" + // Enable scroll option + // Navigate to Text colour and add a colour + // and ensure it is scrolling + // Click on deploy and check if it scrollable and colour selected is visible + }); + + it("Adding text Size to the Text along with BG colour ", function() { + // Navigate to application + // Drag and drop a Text Widget + // Navigate to Property pane + // Add a medium text in the "Label" + // Increase the area of the Text Widget + // Navigate to BG colour and add a colour + // Naviaget to "Text Size" + // Select Paragarph option + // Ensure the text size varies accordingly + }); + + it("Adding Bold Font style and Centre Text Alignment ", function() { + // Navigate to application + // Drag and drop a Text Widget + // Navigate to Property pane + // Add a medium text in the "Label" + // Increase the area of the Text Widget + // Navigate to Font Style + // Make it Bold + // and Navigate to Alignment and make it centre + // Ensure the changes are visible to user + }); + + it("Adding Italic Font style and Text Alignment to exsisting text widget ", function() { + // Navigate to already exsisting Text widget + // Ensure the text is added + // Navigate to Property pane + // Navigate to Font Style + // Make it Italic font + // and Navigate to Alignment and make it Right + // Ensure the changes are visible to user + }); + + it("Expand and Contract text widget Property pane", function() { + // Navigate to already exsisting Text widget + // Navigate to Property pane + // Click on collapse option + // Observe that the property pane is contracted + // Now click again on the arrow + //and ensure it collapses + }); + + it("Copy and paste a text widget", function() { + // Navigate to already exsisting Text widget + // Ensure Clour and font feature exsists + // Copy and paste the widget + // Ensure the new widget retrives the feature exsisting from parent widget + }); + + it("Rename and search a text widget", function() { + // Ensure there are multiple Text widget + // Navigate to Entity Explorer + // Search for "Text" keyword + // Click on one of the text widget + // Rename the text widget from the Entity explorer + // Clear the search keyword + // enter the new text widget name + // and observe the user is navigated to same text widget and properties of the widget does not change on renaming + }); + + it("Search and delete a text widget", function() { + // Ensure there are multiple Text widget + // Navigate to Entity Explorer + // Search for "Text" keyword + // Click on one of the text widget + // Ensure user is navigated to Text widget + // Click on Delete option + // Ensure the Text widget is delete + // Click on Deploy adn ensure the Widget is delete + }); + + it("Search and delete a text widget", function() { + // Ensure there are multiple Text widget + // Navigate to Entity Explorer + // Search for "Text" keyword + // Click on one of the text widget + // Ensure user is navigated to Text widget + // Click on Delete option + // Ensure the Text widget is delete + // Click on Deploy adn ensure the Widget is delete + }); +}); diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index b8ad76996a..c1773bdcd7 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -20,23 +20,30 @@ const apiwidget = require("../locators/apiWidgetslocator.json"); const dynamicInputLocators = require("../locators/DynamicInput.json"); const explorer = require("../locators/explorerlocators.json"); const datasource = require("../locators/DatasourcesEditor.json"); +const viewWidgetsPage = require("../locators/ViewWidgets.json"); let pageidcopy = " "; -Cypress.Commands.add("createOrg", (orgName) => { +export const initLocalstorage = () => { + cy.window().then((window) => { + window.localStorage.setItem("ShowCommentsButtonToolTip", ""); + }); +}; + +Cypress.Commands.add("createOrg", () => { cy.get(homePage.createOrg) .should("be.visible") .first() .click({ force: true }); - cy.xpath(homePage.inputOrgName) +}); + +Cypress.Commands.add("renameOrg", (orgName, newOrgName) => { + cy.contains(orgName).click({ force: true }); + cy.get(homePage.renameOrgInput) .should("be.visible") - .type(orgName); - cy.xpath(homePage.submitBtn).click(); - cy.wait("@applications").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); + .type(newOrgName) + .type("{enter}"); + cy.contains(newOrgName); }); Cypress.Commands.add( @@ -66,6 +73,16 @@ Cypress.Commands.add("navigateToOrgSettings", (orgName) => { cy.get(homePage.inviteUserMembersPage).should("be.visible"); }); +Cypress.Commands.add("openOrgOptionsPopup", (orgName) => { + cy.get(homePage.orgList.concat(orgName).concat(")")) + .scrollIntoView() + .should("be.visible"); + cy.get(".t--org-name span") + .contains(orgName) + .first() + .click({ force: true }); +}); + Cypress.Commands.add("inviteUserForOrg", (orgName, email, role) => { cy.stubPostHeaderReq(); cy.get(homePage.orgList.concat(orgName).concat(")")) @@ -157,7 +174,11 @@ Cypress.Commands.add("deleteUserFromOrg", (orgName, email) => { "response.body.responseMeta.status", 200, ); - cy.get(homePage.DeleteBtn).click({ force: true }); + cy.get(homePage.DeleteBtn) + .last() + .click({ force: true }); + cy.get(homePage.leaveOrgConfirmModal).should("be.visible"); + cy.get(homePage.leaveOrgConfirmButton).click({ force: true }); cy.xpath(homePage.appHome) .first() .should("be.visible") @@ -259,6 +280,11 @@ Cypress.Commands.add("CreateAppInFirstListedOrg", (appname) => { "response.body.responseMeta.status", 200, ); + /* The server created app always has an old dsl so the layout will migrate + * To avoid race conditions between that update layout and this one + * we wait for that to finish before updating layout here + */ + cy.wait("@updateLayout"); }); Cypress.Commands.add( @@ -286,6 +312,13 @@ Cypress.Commands.add( }, ); +Cypress.Commands.add("addBasicProfileDetails", (username, password) => { + cy.get(datasource.authType).click(); + cy.xpath(datasource.basic).click(); + cy.get(datasource.basicUsername).type(username); + cy.get(datasource.basicPassword).type(password); +}); + Cypress.Commands.add("firestoreDatasourceForm", () => { cy.get(datasourceEditor.datasourceConfigUrl).type( datasourceFormData["database-url"], @@ -346,6 +379,8 @@ Cypress.Commands.add("LogintoApp", (uname, pword) => { "response.body.responseMeta.status", 200, ); + + initLocalstorage(); }); Cypress.Commands.add("LoginFromAPI", (uname, pword) => { @@ -438,10 +473,9 @@ Cypress.Commands.add("SearchApp", (appname) => { }); Cypress.Commands.add("SearchEntity", (apiname1, apiname2) => { - cy.get(commonlocators.entityExplorersearch).should("be.visible"); cy.get(commonlocators.entityExplorersearch) - .clear() - .type(apiname1); + .clear({ force: true }) + .type(apiname1, { force: true }); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(500); cy.get( @@ -453,10 +487,10 @@ Cypress.Commands.add("SearchEntity", (apiname1, apiname2) => { }); Cypress.Commands.add("GlobalSearchEntity", (apiname1) => { - cy.get(commonlocators.entityExplorersearch).should("be.visible"); + // entity explorer search will be hidden cy.get(commonlocators.entityExplorersearch) - .clear() - .type(apiname1); + .clear({ force: true }) + .type(apiname1, { force: true }); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(500); cy.get( @@ -474,10 +508,18 @@ Cypress.Commands.add("ResponseCheck", (textTocheck) => { cy.get(apiwidget.responseText).should("be.visible"); }); +Cypress.Commands.add("ResponseTextCheck", (textTocheck) => { + cy.ResponseCheck(); + cy.get(apiwidget.responseText).contains(textTocheck); +}); + Cypress.Commands.add("NavigateToAPI_Panel", () => { cy.get(pages.addEntityAPI) .should("be.visible") .click({ force: true }); + cy.get(pages.integrationCreateNew) + .should("be.visible") + .click({ force: true }); cy.get("#loading").should("not.exist"); }); @@ -608,8 +650,7 @@ Cypress.Commands.add("SelectAction", (action) => { }); Cypress.Commands.add("ClearSearch", () => { - cy.get(commonlocators.entityExplorersearch).should("be.visible"); - cy.get(commonlocators.entityExplorersearch).clear(); + cy.get(commonlocators.entityExplorersearch).clear({ force: true }); }); Cypress.Commands.add( @@ -623,8 +664,8 @@ Cypress.Commands.add( const lastChar = text.slice(-1); cy.get(commonlocators.entityExplorersearch) - .clear() - .click() + .clear({ force: true }) + .click({ force: true }) .then(() => { $element.text(subString); $element.val(subString); @@ -634,10 +675,9 @@ Cypress.Commands.add( ); Cypress.Commands.add("SearchEntityandOpen", (apiname1) => { - cy.get(commonlocators.entityExplorersearch).should("be.visible"); cy.get(commonlocators.entityExplorersearch) - .clear() - .type(apiname1); + .clear({ force: true }) + .type(apiname1, { force: true }); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(500); cy.get( @@ -649,21 +689,20 @@ Cypress.Commands.add("SearchEntityandOpen", (apiname1) => { }); Cypress.Commands.add("enterDatasourceAndPath", (datasource, path) => { - cy.get(apiwidget.resourceUrl) - .first() - .click({ force: true }) - .type(datasource); - /* - cy.xpath(apiwidget.autoSuggest) - .first() - .click({ force: true }); - */ + cy.enterDatasource(datasource); cy.get(apiwidget.editResourceUrl) .first() .click({ force: true }) .type(path, { parseSpecialCharSequences: false }); }); +Cypress.Commands.add("enterDatasource", (datasource) => { + cy.get(apiwidget.resourceUrl) + .first() + .click({ force: true }) + .type(datasource); +}); + Cypress.Commands.add("changeZoomLevel", (zoomValue) => { cy.get(commonlocators.changeZoomlevel) .last() @@ -714,16 +753,14 @@ Cypress.Commands.add( "EnterSourceDetailsWithHeader", (baseUrl, v1method, hKey, hValue) => { cy.enterDatasourceAndPath(baseUrl, v1method); - cy.xpath(apiwidget.headerKey) + cy.get(apiwidget.headerKey) .first() .click({ force: true }) - .type(hKey, { force: true }) - .should("have.value", hKey); - cy.xpath(apiwidget.headerValue) + .type(hKey, { parseSpecialCharSequences: true }); + cy.get(apiwidget.headerValue) .first() .click({ force: true }) - .type(hValue, { force: true }) - .should("have.value", hValue); + .type(hValue, { parseSpecialCharSequences: true }); cy.WaitAutoSave(); }, ); @@ -816,22 +853,20 @@ Cypress.Commands.add( "EnterSourceDetailsWithQueryParam", (baseUrl, v1method, hKey, hValue, qKey, qValue) => { cy.enterDatasourceAndPath(baseUrl, v1method); - cy.xpath(apiwidget.headerKey) + cy.get(apiwidget.headerKey) .first() .click({ force: true }) - .type(hKey, { force: true }) - .should("have.value", hKey); - cy.xpath(apiwidget.headerValue) + .type(hKey, { parseSpecialCharSequences: true }); + cy.get(apiwidget.headerValue) .first() .click({ force: true }) - .type(hValue, { force: true }) - .should("have.value", hValue); - cy.xpath(apiwidget.queryKey) + .type(hValue, { parseSpecialCharSequences: true }); + cy.get(apiwidget.queryKey) .first() .click({ force: true }) .type(qKey, { force: true }) .should("have.value", qKey); - cy.xpath(apiwidget.queryValue) + cy.get(apiwidget.queryValue) .first() .click({ force: true }) .type(qValue, { force: true }) @@ -852,6 +887,9 @@ Cypress.Commands.add( Cypress.Commands.add("CreationOfUniqueAPIcheck", (apiname) => { cy.get(pages.addEntityAPI).click(); + cy.get(pages.integrationCreateNew) + .should("be.visible") + .click({ force: true }); cy.get(apiwidget.createapi).click({ force: true }); cy.wait("@createNewApi"); // cy.wait("@getUser"); @@ -859,9 +897,9 @@ Cypress.Commands.add("CreationOfUniqueAPIcheck", (apiname) => { cy.get(apiwidget.ApiName).click({ force: true }); cy.get(apiwidget.apiTxt) .clear() - .type(apiname, { force: true }) - .should("have.value", apiname) - .focus(); + .focus() + .type(apiname, { force: true, delay: 500 }) + .should("have.value", apiname); cy.get(".error-message").should(($x) => { console.log($x); expect($x).contain(apiname.concat(" is already being used.")); @@ -1000,31 +1038,23 @@ Cypress.Commands.add("DeleteAPI", (apiname) => { ); }); -// Cypress.Commands.add("CreateModal", () => { -// cy.get(modalWidgetPage.selectModal).click(); -// cy.get(modalWidgetPage.createModalButton).click({ force: true }); -// cy.get(modalWidgetPage.controlModalType) -// .find(".bp3-button") -// .click({ force: true }) -// .get("ul.bp3-menu") -// .children() -// .contains("Alert Modal") -// .click(); -// cy.get(modalWidgetPage.controlModalType) -// .find(".bp3-button > .bp3-button-text") -// .should("have.text", "Alert Modal"); -// cy.get(commonlocators.editPropCrossButton).click({force: true}); -// cy.reload(); -// }); +Cypress.Commands.add("AddActionWithModal", () => { + cy.get(commonlocators.dropdownSelectButton) + .last() + .click(); + cy.get(".single-select") + .contains("Open Modal") + .click({ force: true }); + cy.get(modalWidgetPage.selectModal).click(); + cy.get(modalWidgetPage.createModalButton).click({ force: true }); + cy.get(commonlocators.editPropCrossButton).click({ force: true }); +}); Cypress.Commands.add("createModal", (modalType, ModalName) => { - cy.get(widgetsPage.buttonOnClick) - .last() + cy.get(widgetsPage.actionSelect) + .first() .click({ force: true }); - cy.get("ul.bp3-menu") - .children() - .contains("Open Modal") - .click(); + cy.selectOnClickOption("Open Modal"); cy.get(modalWidgetPage.selectModal).click(); cy.get(modalWidgetPage.createModalButton).click({ force: true }); @@ -1057,14 +1087,18 @@ Cypress.Commands.add("createModal", (modalType, ModalName) => { cy.get(".bp3-overlay-backdrop").click({ force: true }); }); +Cypress.Commands.add("selectOnClickOption", (option) => { + cy.get("ul.bp3-menu div.bp3-fill") + .wait(500) + .contains(option) + .click({ force: true }); +}); + Cypress.Commands.add("updateModal", (modalType, ModalName) => { - cy.get(".t--open-dropdown-Select-Action") + cy.get(widgetsPage.actionSelect) .first() .click({ force: true }); - cy.get("ul.bp3-menu") - .children() - .contains("Open Modal") - .click(); + cy.selectOnClickOption("Open Modal"); cy.get(modalWidgetPage.selectModal).click(); cy.get(modalWidgetPage.createModalButton).click({ force: true }); @@ -1110,6 +1144,13 @@ Cypress.Commands.add("UncheckWidgetProperties", (checkboxCss) => { cy.assertPageSave(); }); +Cypress.Commands.add("EditWidgetPropertiesUsingJS", (checkboxCss, inputJS) => { + cy.get(checkboxCss) + .click() + .type(inputJS); + cy.assertPageSave(); +}); + Cypress.Commands.add( "ChangeTextStyle", (dropDownValue, textStylecss, labelName) => { @@ -1132,7 +1173,7 @@ Cypress.Commands.add("widgetText", (text, inputcss, innercss) => { cy.get(inputcss) .first() .trigger("mouseover", { force: true }); - cy.get(innercss).should("have.text", text); + cy.contains(innercss, text); }); Cypress.Commands.add("editColName", (text) => { @@ -1609,15 +1650,15 @@ Cypress.Commands.add("addDsl", (dsl) => { //Fetch the layout id cy.server(); cy.request("GET", "api/v1/pages/" + pageid).then((response) => { - const len = JSON.stringify(response.body); - cy.log(len); - layoutId = JSON.parse(len).data.layouts[0].id; - // Dumpimg the DSL to the created page + const respBody = JSON.stringify(response.body); + layoutId = JSON.parse(respBody).data.layouts[0].id; + // Dumping the DSL to the created page cy.request( "PUT", "api/v1/layouts/" + layoutId + "/pages/" + pageid, dsl, ).then((response) => { + cy.log(response.body); expect(response.status).equal(200); cy.reload(); }); @@ -1691,9 +1732,7 @@ Cypress.Commands.add("getAlert", (alertcss) => { cy.get(alertcss) .click({ force: true }) - .type("{command}{A}{del}") - .type("hello") - .should("not.to.be.empty"); + .type("hello"); cy.get(".t--open-dropdown-Select-type").click({ force: true }); cy.get(".bp3-popover-content .bp3-menu li") .contains("Success") @@ -1723,9 +1762,7 @@ Cypress.Commands.add( cy.get(alertcss) .click({ force: true }) - .type("{command}{A}{del}") - .type("hello") - .should("not.to.be.empty"); + .type("hello"); cy.get(".t--open-dropdown-Select-type").click({ force: true }); cy.get(".bp3-popover-content .bp3-menu li") .contains("Success") @@ -1733,17 +1770,20 @@ Cypress.Commands.add( }, ); +Cypress.Commands.add("addQueryFromLightningMenu", (QueryName) => { + cy.get(commonlocators.dropdownSelectButton) + .first() + .click({ force: true }) + .selectOnClickOption("Execute a Query") + .selectOnClickOption(QueryName); +}); + Cypress.Commands.add("addAPIFromLightningMenu", (ApiName) => { cy.get(commonlocators.dropdownSelectButton) + .first() .click({ force: true }) - .get("ul.bp3-menu") - .children() - .contains("Call An API") - .click({ force: true }) - .get("ul.bp3-menu") - .children() - .contains(ApiName) - .click({ force: true }); + .selectOnClickOption("Execute a Query") + .selectOnClickOption(ApiName); }); Cypress.Commands.add("radioInput", (index, text) => { @@ -1815,7 +1855,11 @@ Cypress.Commands.add("testSaveDeleteDatasource", () => { 200, ); - cy.get(datasourceEditor.editDatasource).click(); + cy.get( + `${datasourceEditor.datasourceCard} ${datasourceEditor.editDatasource}`, + ) + .last() + .click(); cy.get(".t--delete-datasource").click(); cy.wait("@deleteDatasource").should( @@ -1838,7 +1882,9 @@ Cypress.Commands.add("NavigateToDatasourceEditor", () => { cy.get(explorer.addDBQueryEntity) .last() .click({ force: true }); - cy.get(queryEditor.addDatasource).click(); + cy.get(pages.integrationCreateNew) + .should("be.visible") + .click({ force: true }); }); Cypress.Commands.add("NavigateToQueryEditor", () => { @@ -1859,16 +1905,20 @@ Cypress.Commands.add("testDatasource", () => { Cypress.Commands.add("saveDatasource", () => { cy.get(".t--save-datasource").click(); - cy.wait("@saveDatasource").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); + cy.wait("@saveDatasource") + .then((xhr) => { + cy.log(JSON.stringify(xhr.response.body)); + }) + .should("have.nested.property", "response.body.responseMeta.status", 200); }); Cypress.Commands.add("testSaveDatasource", () => { cy.saveDatasource(); - cy.get(datasourceEditor.editDatasource).click(); + cy.get( + `${datasourceEditor.datasourceCard} ${datasourceEditor.editDatasource}`, + ) + .last() + .click(); cy.testDatasource(); }); @@ -1926,7 +1976,9 @@ Cypress.Commands.add("createPostgresDatasource", () => { Cypress.Commands.add("deleteDatasource", (datasourceName) => { cy.NavigateToQueryEditor(); - + cy.get(pages.integrationActiveTab) + .should("be.visible") + .click({ force: true }); cy.contains(".t--datasource-name", datasourceName) .find(".t--edit-datasource") .click(); @@ -1986,7 +2038,8 @@ Cypress.Commands.add("runAndDeleteQuery", () => { 200, ); - cy.get(queryEditor.deleteQuery).click(); + cy.get(queryEditor.queryMoreAction).click(); + cy.get(queryEditor.deleteUsingContext).click(); cy.wait("@deleteAction").should( "have.nested.property", "response.body.responseMeta.status", @@ -2000,8 +2053,8 @@ Cypress.Commands.add("dragAndDropToCanvas", (widgetType, { x, y }) => { .trigger("mousedown", { button: 0 }, { force: true }) .trigger("mousemove", x, y, { force: true }); cy.get(explorer.dropHere) - .click() - .trigger("mouseup", { force: true }); + .trigger("mousemove", x, y) + .trigger("mouseup", x, y + 20); }); Cypress.Commands.add("executeDbQuery", (queryName) => { @@ -2010,7 +2063,7 @@ Cypress.Commands.add("executeDbQuery", (queryName) => { .click({ force: true }) .get("ul.bp3-menu") .children() - .contains("Execute a DB Query") + .contains("Execute a Query") .click({ force: true }) .get("ul.bp3-menu") .children() @@ -2018,25 +2071,175 @@ Cypress.Commands.add("executeDbQuery", (queryName) => { .click({ force: true }); }); +Cypress.Commands.add("CreateMockQuery", (queryName) => { + // cy.get(queryEditor.addNewQueryBtn).click({ force: true }); + cy.get(queryEditor.createQuery) + .first() + .click({ force: true }); + cy.get(queryEditor.queryNameField).type(queryName + "{enter}", { + force: true, + }); + cy.assertPageSave(); + cy.get(queryEditor.templateMenu + " div") + .contains("Select") + .click({ force: true }); + cy.runQuery(); + // cy.wait(3000); + // cy.get(queryEditor.runQuery) + // .click({force: true}); +}); + Cypress.Commands.add("openPropertyPane", (widgetType) => { const selector = `.t--draggable-${widgetType}`; cy.get(selector) .first() .trigger("mouseover", { force: true }) .wait(500); - cy.get(`${selector}:first-of-type .t--widget-propertypane-toggle`) + cy.get( + `${selector}:first-of-type .t--widget-propertypane-toggle > .t--widget-name`, + ) .first() .click({ force: true }); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(1000); }); +Cypress.Commands.add("openPropertyPaneCopy", (widgetType) => { + const selector = `.t--draggable-${widgetType}`; + cy.get(selector) + .last() + .trigger("mouseover", { force: true }) + .wait(500); + cy.get( + `${selector}:first-of-type .t--widget-propertypane-toggle > .t--widget-name`, + ) + .first() + .click({ force: true }); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(1000); +}); + +Cypress.Commands.add("changeButtonStyle", (index, buttonColor, hoverColor) => { + cy.get(widgetsPage.buttonStyleDropdown).click({ force: true }); + cy.get( + ".bp3-popover-content .t--dropdown-option:nth-child(" + index + ")", + ).click({ force: true }); + cy.PublishtheApp(); + cy.get(widgetsPage.widgetBtn).should( + "have.css", + "background-color", + buttonColor, + ); + // cy.get(buttonBackground) + // .first() + // .trigger('mouseover', { force: true }); + // cy.get(buttonBackground) + // .should('have.css', 'background-color', hoverColor); + cy.wait(1000); +}); + Cypress.Commands.add("closePropertyPane", () => { cy.get(commonlocators.editPropCrossButton).click({ force: true }); }); +Cypress.Commands.add("onClickActions", (forSuccess, forFailure) => { + // Filling the messages for success/failure in the onClickAction of the button widget. + // For Success + cy.get(".code-highlight") + .children() + .contains("No Action") + .first() + .click({ force: true }) + .selectOnClickOption("Show Message") + .get("div.t--property-control-onclick div.CodeMirror-lines") + .click() + .type(forSuccess) + .get("button.t--open-dropdown-Select-type") + .click() + .get("a.single-select div") + .contains(forSuccess) + .click(); + + cy.wait(2000); + // For Failure + cy.get(".code-highlight") + .children() + .contains("No Action") + .last() + .click({ force: true }) + .selectOnClickOption("Show Message") + .get("div.t--property-control-onclick div.CodeMirror-lines") + .last() + .click() + .type(forFailure) + .get("button.t--open-dropdown-Select-type") + .last() + .click() + .get("a.single-select div") + .contains(forFailure) + .click(); +}); + +Cypress.Commands.add("copyWidget", (widget, widgetLocator) => { + const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl"; + //Copy widget and verify all properties + cy.get(widgetsPage.propertypaneText) + .children() + .last() + .invoke("text") + .then((x) => { + cy.log(x); + let originalWidget = x.replaceAll("x", ""); + originalWidget = originalWidget.replaceAll(/\u200B/g, ""); + cy.log(originalWidget); + cy.get(widgetsPage.copyWidget).click({ force: true }); + cy.reload(); + // Wait for the widget to be appear in the DOM and press Ctrl/Cmd + V to paste the button. + cy.get(widgetLocator).should("be.visible"); + cy.get("body").type(`{${modifierKey}}v`); + cy.wait(2000); + cy.openPropertyPaneCopy(widget); + cy.get(widgetsPage.propertypaneText) + .children() + .last() + .invoke("text") + .then((y) => { + cy.log(y); + let copiedWidget = y.replaceAll("x", ""); + copiedWidget = copiedWidget.replaceAll(/\u200B/g, ""); + cy.log(copiedWidget); + expect(originalWidget).to.be.equal(copiedWidget); + }); + }); +}); + +Cypress.Commands.add("deleteWidget", (widget) => { + // Delete the button widget + cy.get(widgetsPage.removeWidget).click({ force: true }); + cy.get(widgetsPage.deleteToast).should("have.text", "UNDO"); +}); + +Cypress.Commands.add("UpdateChartType", (typeOfChart) => { + // Command to change the chart type if the property pane of the chart widget is opened. + cy.get(viewWidgetsPage.chartType) + .last() + .click({ force: true }); + cy.get(commonlocators.dropdownmenu) + .children() + .contains(typeOfChart) + .click({ force: true }); + + cy.get(viewWidgetsPage.chartType + " span.cs-text").should( + "have.text", + typeOfChart, + ); +}); + Cypress.Commands.add("createAndFillApi", (url, parameters) => { cy.NavigateToApiEditor(); + cy.get(pages.integrationCreateNew) + .should("be.visible") + .click({ force: true }); cy.testCreateApiButton(); cy.get("@createNewApi").then((response) => { cy.get(ApiEditor.ApiNameField).should("be.visible"); @@ -2187,6 +2390,8 @@ Cypress.Commands.add("startServerAndRoutes", () => { cy.route("PUT", "/api/v1/actions/move").as("moveAction"); cy.route("POST", "/api/v1/organizations").as("createOrg"); + cy.route("POST", "api/v1/applications/import/*").as("importNewApplication"); + cy.route("GET", "api/v1/applications/export/*").as("exportApplication"); cy.route("GET", "/api/v1/organizations/roles?organizationId=*").as( "getRoles", ); @@ -2200,6 +2405,7 @@ Cypress.Commands.add("startServerAndRoutes", () => { cy.route("POST", "/api/v1/organizations/*/logo").as("updateLogo"); cy.route("DELETE", "/api/v1/organizations/*/logo").as("deleteLogo"); cy.route("POST", "/api/v1/applications/*/fork/*").as("postForkAppOrg"); + cy.route("PUT", "/api/v1/users/leaveOrganization/*").as("leaveOrgApiCall"); }); Cypress.Commands.add("alertValidate", (text) => { @@ -2242,8 +2448,6 @@ Cypress.Commands.add("assertEvaluatedValuePopup", (expectedType) => { cy.get(dynamicInputLocators.evaluatedValue) .should("be.visible") .children("p") - .should("contain.text", "Expected Data Type") - .should("contain.text", "Evaluated Value") .siblings("pre") .should("have.text", expectedType); }); @@ -2319,15 +2523,33 @@ Cypress.Commands.add("ValidatePaginationInputData", () => { Cypress.Commands.add("callApi", (apiname) => { cy.get(commonlocators.callApi) .first() - .click(); + .click({ force: true }); cy.get(commonlocators.singleSelectMenuItem) - .contains("Call An API") - .click(); + .contains("Execute a Query") + .click({ force: true }); cy.get(commonlocators.selectMenuItem) .contains(apiname) - .click(); + .click({ force: true }); }); Cypress.Commands.add("assertPageSave", () => { cy.get(commonlocators.saveStatusSuccess); }); + +Cypress.Commands.add("ValidateQueryParams", (param) => { + cy.xpath(apiwidget.paramsTab) + .should("be.visible") + .click({ force: true }); + + cy.validateCodeEditorContent(apiwidget.paramKey, param.key); + cy.validateCodeEditorContent(apiwidget.paramValue, param.value); +}); + +Cypress.Commands.add( + "validateCodeEditorContent", + (selector, contentToValidate) => { + cy.get(selector).within(() => { + cy.get(".CodeMirror-code").should("have.text", contentToValidate); + }); + }, +); diff --git a/app/client/cypress/support/index.js b/app/client/cypress/support/index.js index 71c1be4d99..bf914f1c91 100644 --- a/app/client/cypress/support/index.js +++ b/app/client/cypress/support/index.js @@ -18,6 +18,7 @@ let appId; // Import commands.js using ES2015 syntax: import "./commands"; +import { initLocalstorage } from "./commands"; Cypress.on("uncaught:exception", (err, runnable) => { // returning false here prevents Cypress from @@ -31,11 +32,13 @@ Cypress.on("fail", (error, runnable) => { }); before(function() { + initLocalstorage(); cy.startServerAndRoutes(); // Clear indexedDB cy.window().then((window) => { window.indexedDB.deleteDatabase("Appsmith"); }); + const username = Cypress.env("USERNAME"); const password = Cypress.env("PASSWORD"); cy.LoginFromAPI(username, password); @@ -58,6 +61,7 @@ before(function() { }); beforeEach(function() { + initLocalstorage(); Cypress.Cookies.preserveOnce("SESSION", "remember_token"); cy.startServerAndRoutes(); }); diff --git a/app/client/cypress/test.sh b/app/client/cypress/test.sh index b1a36fb9d4..f8c75f22cc 100755 --- a/app/client/cypress/test.sh +++ b/app/client/cypress/test.sh @@ -27,10 +27,10 @@ if [ "$target" == "ci" ]; then # On the CI server run the tests in parallel # This requires the projectId and the record_key to be configured in your environment variables. By default this is defined on the CI server echo "Got the Build ID: $BUILD_ID" - $(npm bin)/cypress run --headless --browser chrome \ + $(npm bin)/cypress run --headless \ --record --key "$CYPRESS_RECORD_KEY" --ci-build-id $BUILD_ID \ --parallel --group "Electrons on Gitlab CI" \ --spec "cypress/integration/Smoke_TestSuite/**/*.js" else - $(npm bin)/cypress run --headless --browser chrome --spec "cypress/integration/Smoke_TestSuite/**/*.js" + $(npm bin)/cypress run --headless --browser chromium --spec "cypress/integration/Smoke_TestSuite/**/*.js" fi diff --git a/app/client/docker/templates/nginx-app.conf.template b/app/client/docker/templates/nginx-app.conf.template index 4b7c5fdf4e..6b8ed93474 100644 --- a/app/client/docker/templates/nginx-app.conf.template +++ b/app/client/docker/templates/nginx-app.conf.template @@ -48,6 +48,7 @@ server { sub_filter __APPSMITH_MAIL_ENABLED__ '${APPSMITH_MAIL_ENABLED}'; sub_filter __APPSMITH_DISABLE_TELEMETRY__ '${APPSMITH_DISABLE_TELEMETRY}'; sub_filter __APPSMITH_CLOUD_SERVICES_BASE_URL__ '${APPSMITH_CLOUD_SERVICES_BASE_URL}'; + sub_filter __APPSMITH_RECAPTCHA_SITE_KEY__ '${APPSMITH_RECAPTCHA_SITE_KEY}'; } location /f { diff --git a/app/client/jest.config.js b/app/client/jest.config.js index c88bf5f8ec..f390ccddc2 100644 --- a/app/client/jest.config.js +++ b/app/client/jest.config.js @@ -27,6 +27,9 @@ module.exports = { "test/(.*)": "/test/$1", }, globals: { + "ts-jest": { + isolatedModules: true, + }, APPSMITH_FEATURE_CONFIGS: { sentry: { dsn: parseConfig("__APPSMITH_SENTRY_DSN__"), diff --git a/app/client/netlify.toml b/app/client/netlify.toml index 435adaafc5..4f0c0edc20 100644 --- a/app/client/netlify.toml +++ b/app/client/netlify.toml @@ -8,35 +8,20 @@ REACT_APP_ALGOLIA_SEARCH_INDEX_NAME = "test_appsmith" REACT_APP_CLIENT_LOG_LEVEL = "debug" REACT_APP_GOOGLE_MAPS_API_KEY = "AIzaSyBOQFulljufGt3VDhBAwNjZN09KEFufVyg" + REACT_APP_GOOGLE_RECAPTCHA_SITE_KEY = "" REACT_APP_TNC_PP = "true" REACT_APP_CLOUD_HOSTING = "true" REACT_APP_INTERCOM_APP_ID = "y10e7138" REACT_APP_MAIL_ENABLED = "true" REACT_APP_SENTRY_DSN = "https://abf15a075d1347969df44c746cca7eaa@o296332.ingest.sentry.io/1546547" REACT_APP_SENTRY_ENVIRONMENT = "Production" + REACT_APP_SHOW_ONBOARDING_FORM = "true" SENTRY_AUTH_TOKEN = "dfdf7fa46c5b483a944b4571554d6466da3c64a6ed8b46e3b8a4285183a6bcc3" SENTRY_DSN = "https://abf15a075d1347969df44c746cca7eaa@o296332.ingest.sentry.io/1546547" SENTRY_ORG = "appsmith" SENTRY_PROJECT = "appsmith" SENTRY_LOG_LEVEL = "info" -[context.production] - [context.production.environment] - REACT_APP_ENVIRONMENT = "PRODUCTION" - REACT_APP_SENTRY_ENVIRONMENT = "Production" - REACT_APP_SMART_LOOK_ID = "c370af0df0edf38360adbefbdc47d2b42ea137c9" - REACT_APP_SEGMENT_KEY = "9OnZ6LnDztuZZo4zXfoutEEBB2wftHUH" - REACT_APP_BASE_URL = "https://api.appsmith.com" - APP_HOST = "app.appsmith.com" - REACT_APP_CLIENT_LOG_LEVEL = "error" - -[context.release] - [context.release.environment] - REACT_APP_ENVIRONMENT = "STAGING" - REACT_APP_SENTRY_ENVIRONMENT = "Staging" - REACT_APP_BASE_URL = "https://release.app.appsmith.com" - APP_HOST = "release.app.appsmith.com" - [context.deploy-preview] [context.deploy-preview.environment] REACT_APP_ENVIRONMENT = "STAGING" diff --git a/app/client/package.json b/app/client/package.json index bc24cf83d9..f2ee5e6319 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -16,7 +16,7 @@ "@blueprintjs/timezone": "^3.6.0", "@craco/craco": "^5.7.0", "@draft-js-plugins/editor": "^4.1.0", - "@draft-js-plugins/mention": "^4.3.2", + "@draft-js-plugins/mention": "^4.5.1", "@fusioncharts/powercharts": "^3.16.0", "@github/g-emoji-element": "^1.1.5", "@manaflair/redux-batch": "^1.0.0", @@ -40,10 +40,12 @@ "@types/react-table": "^7.0.13", "@types/styled-components": "^5.1.3", "@types/tinycolor2": "^1.4.2", + "@types/zipcelx": "^1.5.0", "@uppy/core": "^1.16.0", "@uppy/dashboard": "^1.16.0", "@uppy/file-input": "^1.4.22", "@uppy/google-drive": "^1.5.22", + "@uppy/image-editor": "^0.2.4", "@uppy/onedrive": "^1.1.22", "@uppy/react": "^1.11.2", "@uppy/url": "^1.5.16", @@ -61,11 +63,11 @@ "deep-diff": "^1.0.2", "downloadjs": "^1.4.7", "draft-js": "^0.11.7", - "emoji-picker-react": "^3.4.2", + "emoji-mart": "^3.0.1", "eslint": "^7.11.0", "fast-deep-equal": "^3.1.1", "fast-xml-parser": "^3.17.5", - "flow-bin": "^0.91.0", + "flow-bin": "^0.148.0", "fuse.js": "^3.4.5", "fusioncharts": "^3.16.0", "history": "^4.10.1", @@ -73,13 +75,14 @@ "immer": "^8.0.1", "instantsearch.css": "^7.4.2", "instantsearch.js": "^4.4.1", - "interweave": "^12.1.1", - "interweave-autolink": "^4.0.1", + "interweave": "^12.7.2", + "interweave-autolink": "^4.4.2", "js-sha256": "^0.9.0", + "jshint": "^2.12.0", "json-fn": "^1.1.1", "lint-staged": "^9.2.5", "localforage": "^1.7.3", - "lodash": "^4.17.19", + "lodash": "^4.17.21", "lodash-es": "4.17.14", "lodash-move": "^1.1.1", "loglevel": "^1.6.7", @@ -88,12 +91,13 @@ "moment": "^2.24.0", "moment-timezone": "^0.5.27", "nanoid": "^2.0.4", - "node-sass": "^4.11.0", + "node-forge": "^0.10.0", + "node-sass": "^6.0.1", "normalizr": "^3.3.0", "path-to-regexp": "^6.2.0", "popper.js": "^1.15.0", "prettier": "^1.18.2", - "prismjs": "^1.23.0", + "prismjs": "^1.24.0", "rc-pagination": "^3.1.3", "re-reselect": "^3.4.0", "react": "^16.12.0", @@ -105,6 +109,7 @@ "react-dnd-touch-backend": "^9.4.0", "react-dom": "^16.7.0", "react-google-maps": "^9.4.5", + "react-google-recaptcha": "^2.1.0", "react-helmet": "^5.2.1", "react-infinite-scroller": "^1.2.4", "react-instantsearch-dom": "^6.4.0", @@ -112,6 +117,7 @@ "react-mentions": "^4.1.1", "react-paginating": "^1.4.0", "react-player": "^2.3.1", + "react-rating": "^2.0.5", "react-redux": "^7.1.3", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", @@ -123,6 +129,7 @@ "react-toastify": "^5.5.0", "react-transition-group": "^4.3.0", "react-use-gesture": "^7.0.4", + "react-virtuoso": "^1.9.0", "react-window": "^1.8.6", "react-zoom-pan-pinch": "^1.6.1", "redux": "^4.0.1", @@ -144,12 +151,12 @@ "typescript": "^4.1.3", "unescape-js": "^1.1.4", "url-search-params-polyfill": "^8.0.0", - "worker-loader": "^3.0.2" + "worker-loader": "^3.0.2", + "zipcelx": "^1.6.2" }, "scripts": { "analyze": "source-map-explorer 'build/static/js/*.js'", - "start": "BROWSER=none EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT HOST=dev.appsmith.com craco start", - "start-m1": "BROWSER=none EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT HOST=0.0.0.0 craco start", + "start": "BROWSER=none EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT REACT_APP_CLIENT_LOG_LEVEL=debug HOST=dev.appsmith.com craco start", "build": "./build.sh", "build-local": "craco --max-old-space-size=4096 build --config craco.build.config.js", "build-staging": "REACT_APP_ENVIRONMENT=STAGING craco --max-old-space-size=4096 build --config craco.build.config.js", @@ -161,7 +168,8 @@ "test:unit": "$(npm bin)/jest -b --colors --no-cache --coverage --collectCoverage=true --coverageDirectory='../../' --coverageReporters='json-summary'", "test:jest": "$(npm bin)/jest --watch", "storybook": "start-storybook -p 9009 -s public", - "build-storybook": "build-storybook -s public" + "build-storybook": "build-storybook -s public", + "postinstall": "patch-package" }, "resolution": { "jest": "24.8.0" @@ -195,15 +203,20 @@ "@types/deep-diff": "^1.0.0", "@types/downloadjs": "^1.4.2", "@types/draft-js": "^0.11.1", + "@types/emoji-mart": "^3.0.4", "@types/jest": "^24.0.22", + "@types/jshint": "^2.12.0", "@types/marked": "^1.2.2", + "@types/node-forge": "^0.10.0", "@types/react-beautiful-dnd": "^11.0.4", + "@types/react-google-recaptcha": "^2.1.1", "@types/react-select": "^3.0.5", "@types/react-tabs": "^2.3.1", "@types/react-test-renderer": "^17.0.1", "@types/react-window": "^1.8.2", "@types/redux-form": "^8.1.9", "@types/redux-mock-store": "^1.0.2", + "@types/resize-observer-browser": "^0.1.5", "@types/styled-system": "^5.1.9", "@types/tern": "0.22.0", "@types/toposort": "^2.0.3", @@ -224,6 +237,7 @@ "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-react": "^7.21.3", "eslint-plugin-react-hooks": "^2.3.0", + "eslint-plugin-sort-destructure-keys": "^1.3.5", "factory.ts": "^0.5.1", "jest-canvas-mock": "^2.3.1", "mocha": "^7.1.0", @@ -231,6 +245,8 @@ "mochawesome": "^5.0.0", "mochawesome-report-generator": "^4.1.0", "msw": "^0.28.0", + "patch-package": "^6.4.7", + "postinstall-postinstall": "^2.1.0", "raw-loader": "^4.0.2", "react-docgen-typescript-loader": "^3.6.0", "react-is": "^16.12.0", diff --git a/app/client/patches/@blueprintjs+core+3.36.0.patch b/app/client/patches/@blueprintjs+core+3.36.0.patch new file mode 100644 index 0000000000..3342cbfefc --- /dev/null +++ b/app/client/patches/@blueprintjs+core+3.36.0.patch @@ -0,0 +1,22 @@ +diff --git a/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js b/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js +index 84f03fa..5e5488a 100644 +--- a/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js ++++ b/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js +@@ -188,7 +188,16 @@ var EditableText = /** @class */ (function (_super) { + if (this.state.isEditing && !prevState.isEditing) { + (_b = (_a = this.props).onEdit) === null || _b === void 0 ? void 0 : _b.call(_a, this.state.value); + } +- this.updateInputDimensions(); ++ // updateInputDimensions is an expensive method. Call it only when the props ++ // it depends on change ++ if (this.state.value !== prevState.value || ++ this.props.alwaysRenderInput !== prevProps.alwaysRenderInput || ++ this.props.maxLines !== prevProps.maxLines || ++ this.props.minLines !== prevProps.minLines || ++ this.props.minWidth !== prevProps.minWidth || ++ this.props.multiline !== prevProps.multiline) { ++ this.updateInputDimensions(); ++ } + }; + EditableText.prototype.renderInput = function (value) { + var _a = this.props, disabled = _a.disabled, maxLength = _a.maxLength, multiline = _a.multiline, type = _a.type, placeholder = _a.placeholder; diff --git a/app/client/public/index.html b/app/client/public/index.html index 558f9ccca9..a022cc7b1c 100755 --- a/app/client/public/index.html +++ b/app/client/public/index.html @@ -33,6 +33,11 @@
+ +
diff --git a/app/client/src/AppRouter.tsx b/app/client/src/AppRouter.tsx index 1ba02d5b77..430762a01b 100644 --- a/app/client/src/AppRouter.tsx +++ b/app/client/src/AppRouter.tsx @@ -13,6 +13,7 @@ import { getApplicationViewerPageURL, ORG_URL, SIGN_UP_URL, + SIGNUP_SUCCESS_URL, USER_AUTH_URL, USERS_URL, PROFILE, @@ -47,7 +48,8 @@ function changeAppBackground(currentTheme: any) { if ( trimTrailingSlash(window.location.pathname) === "/applications" || window.location.pathname.indexOf("/settings/") !== -1 || - trimTrailingSlash(window.location.pathname) === "/profile" + trimTrailingSlash(window.location.pathname) === "/profile" || + trimTrailingSlash(window.location.pathname) === "/signup-success" ) { document.body.style.backgroundColor = currentTheme.colors.homepageBackground; @@ -101,6 +103,11 @@ class AppRouter extends React.Component { exact path={APPLICATIONS_URL} /> + { }; }; +export const runActionViaShortcut = () => { + return { + type: ReduxActionTypes.RUN_ACTION_SHORTCUT_REQUEST, + }; +}; + export const runAction = (id: string, paginationField?: PaginationField) => { return { type: ReduxActionTypes.RUN_ACTION_REQUEST, @@ -117,7 +123,11 @@ export const updateActionSuccess = (payload: { data: Action }) => { }; }; -export const deleteAction = (payload: { id: string; name: string }) => { +export const deleteAction = (payload: { + id: string; + name: string; + onSuccess?: () => void; +}) => { return { type: ReduxActionTypes.DELETE_ACTION_INIT, payload, @@ -233,6 +243,12 @@ export const updateActionProperty = ( }); }; +export const executePageLoadActionsComplete = () => { + return { + type: ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS_COMPLETE, + }; +}; + export const setActionsToExecuteOnPageLoad = ( actions: Array<{ executeOnLoad: boolean; diff --git a/app/client/src/actions/apiPaneActions.ts b/app/client/src/actions/apiPaneActions.ts index ccb0096922..e4bf0466f7 100644 --- a/app/client/src/actions/apiPaneActions.ts +++ b/app/client/src/actions/apiPaneActions.ts @@ -70,3 +70,11 @@ export const updateBodyContentType = ( type: ReduxActionTypes.UPDATE_API_ACTION_BODY_CONTENT_TYPE, payload: { title, apiId }, }); + +export const redirectToNewIntegrations = ( + applicationId: string, + pageId: string, +): ReduxAction<{ applicationId: string; pageId: string }> => ({ + type: ReduxActionTypes.REDIRECT_TO_NEW_INTEGRATIONS, + payload: { applicationId, pageId }, +}); diff --git a/app/client/src/actions/applicationActions.ts b/app/client/src/actions/applicationActions.ts index 4578f5fc11..e4cf360f65 100644 --- a/app/client/src/actions/applicationActions.ts +++ b/app/client/src/actions/applicationActions.ts @@ -1,6 +1,9 @@ import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants"; import { APP_MODE } from "../reducers/entityReducers/appReducer"; -import { UpdateApplicationPayload } from "api/ApplicationApi"; +import { + UpdateApplicationPayload, + ImportApplicationRequest, +} from "api/ApplicationApi"; export const setDefaultApplicationPageSuccess = ( pageId: string, @@ -77,6 +80,13 @@ export const duplicateApplication = (applicationId: string) => { }; }; +export const importApplication = (appDetails: ImportApplicationRequest) => { + return { + type: ReduxActionTypes.IMPORT_APPLICATION_INIT, + payload: appDetails, + }; +}; + export const getAllApplications = () => { return { type: ReduxActionTypes.GET_ALL_APPLICATION_INIT, @@ -88,3 +98,8 @@ export const resetCurrentApplication = () => { type: ReduxActionTypes.RESET_CURRENT_APPLICATION, }; }; + +export const setShowAppInviteUsersDialog = (payload: boolean) => ({ + type: ReduxActionTypes.SET_SHOW_APP_INVITE_USERS_MODAL, + payload, +}); diff --git a/app/client/src/actions/canvasSelectionActions.ts b/app/client/src/actions/canvasSelectionActions.ts new file mode 100644 index 0000000000..e9030b14e2 --- /dev/null +++ b/app/client/src/actions/canvasSelectionActions.ts @@ -0,0 +1,22 @@ +import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants"; +import { SelectedArenaDimensions } from "pages/common/CanvasSelectionArena"; + +export const setCanvasSelectionStateAction = (start: boolean) => { + return { + type: start + ? ReduxActionTypes.START_CANVAS_SELECTION + : ReduxActionTypes.STOP_CANVAS_SELECTION, + }; +}; + +export const selectAllWidgetsInAreaAction = ( + selectionArena: SelectedArenaDimensions, + snapToNextColumn: boolean, + snapToNextRow: boolean, + isMultiSelect: boolean, +): ReduxAction => { + return { + type: ReduxActionTypes.SELECT_WIDGETS_IN_AREA, + payload: { selectionArena, snapToNextColumn, snapToNextRow, isMultiSelect }, + }; +}; diff --git a/app/client/src/actions/commentActions.ts b/app/client/src/actions/commentActions.ts index 4554af5f78..622582fcea 100644 --- a/app/client/src/actions/commentActions.ts +++ b/app/client/src/actions/commentActions.ts @@ -1,5 +1,7 @@ import { ReduxActionTypes } from "constants/ReduxActionConstants"; import { COMMENT_EVENTS_CHANNEL } from "constants/CommentConstants"; +import { options as filterOptions } from "comments/AppComments/AppCommentsFilterPopover"; + import { CommentThread, CommentEventPayload, @@ -11,21 +13,12 @@ import { NewCommentThreadPayload, } from "entities/Comments/CommentsInterfaces"; +import { RawDraftContentState } from "draft-js"; + export const setCommentThreadsRequest = () => ({ type: ReduxActionTypes.SET_COMMENT_THREADS_REQUEST, }); -// todo remove (for dev) -export const setCommentThreadsSuccess = (payload: any) => ({ - type: ReduxActionTypes.SET_COMMENT_THREADS_SUCCESS, - payload, -}); - -// todo remove (for dev) -export const initCommentThreads = () => ({ - type: ReduxActionTypes.INIT_COMMENT_THREADS, -}); - export const commentEvent = (payload: CommentEventPayload) => ({ type: COMMENT_EVENTS_CHANNEL, payload, @@ -81,14 +74,6 @@ export const setCommentMode = (payload: boolean) => ({ payload, }); -export const setIsCommentThreadVisible = (payload: { - commentThreadId: string; - isVisible: boolean; -}) => ({ - type: ReduxActionTypes.SET_IS_COMMENT_THREAD_VISIBLE, - payload, -}); - export const fetchApplicationCommentsRequest = () => ({ type: ReduxActionTypes.FETCH_APPLICATION_COMMENTS_REQUEST, }); @@ -130,7 +115,10 @@ export const updateCommentThreadEvent = (payload: Partial) => ({ payload, }); -export const pinCommentThreadRequest = (payload: { threadId: string }) => ({ +export const pinCommentThreadRequest = (payload: { + threadId: string; + pin: boolean; +}) => ({ type: ReduxActionTypes.PIN_COMMENT_THREAD_REQUEST, payload, }); @@ -158,3 +146,117 @@ export const deleteCommentSuccess = (payload: { type: ReduxActionTypes.DELETE_COMMENT_SUCCESS, payload, }); + +export const setShouldShowResolvedComments = (payload: boolean) => ({ + type: ReduxActionTypes.SET_SHOULD_SHOW_RESOLVED_COMMENTS, + payload, +}); + +export const setAppCommentsFilter = ( + payload: typeof filterOptions[number]["value"], +) => ({ + type: ReduxActionTypes.SET_APP_COMMENTS_FILTER, + payload, +}); + +export const resetVisibleThread = (threadId?: string) => ({ + type: ReduxActionTypes.RESET_VISIBLE_THREAD, + payload: threadId, +}); + +export const setVisibleThread = (threadId: string) => ({ + type: ReduxActionTypes.SET_VISIBLE_THREAD, + payload: threadId, +}); + +export const markThreadAsReadRequest = (threadId: string) => ({ + type: ReduxActionTypes.MARK_THREAD_AS_READ_REQUEST, + payload: { threadId }, +}); + +export const editCommentRequest = ({ + body, + commentId, + commentThreadId, +}: { + commentThreadId: string; + commentId: string; + body: RawDraftContentState; +}) => ({ + type: ReduxActionTypes.EDIT_COMMENT_REQUEST, + payload: { + body, + commentId, + commentThreadId, + }, +}); + +export const updateCommentSuccess = (payload: { + comment: Comment; + commentThreadId: string; +}) => ({ + type: ReduxActionTypes.EDIT_COMMENT_SUCCESS, + payload, +}); + +export const deleteCommentThreadRequest = (commentThreadId: string) => ({ + type: ReduxActionTypes.DELETE_THREAD_REQUEST, + payload: commentThreadId, +}); + +export const deleteCommentThreadSuccess = (payload: { + commentThreadId: string; + appId: string; +}) => ({ + type: ReduxActionTypes.DELETE_THREAD_SUCCESS, + payload, +}); + +export const addCommentReaction = (payload: { + emoji: string; + commentId: string; +}) => ({ + type: ReduxActionTypes.ADD_COMMENT_REACTION, + payload, +}); + +export const removeCommentReaction = (payload: { + emoji: string; + commentId: string; +}) => ({ + type: ReduxActionTypes.REMOVE_COMMENT_REACTION, + payload, +}); + +export const updateCommentEvent = (payload: Comment) => ({ + type: ReduxActionTypes.UPDATE_COMMENT_EVENT, + payload, +}); + +export const showCommentsIntroCarousel = () => ({ + type: ReduxActionTypes.SHOW_COMMENTS_INTRO_CAROUSEL, + payload: undefined, +}); + +export const hideCommentsIntroCarousel = () => ({ + type: ReduxActionTypes.HIDE_COMMENTS_INTRO_CAROUSEL, + payload: undefined, +}); + +export const setAreCommentsEnabled = (flag: boolean) => ({ + type: ReduxActionTypes.SET_ARE_COMMENTS_ENABLED, + payload: flag, +}); + +export const fetchUnreadCommentThreadsCountSuccess = (count?: number) => ({ + type: ReduxActionTypes.FETCH_UNREAD_COMMENT_THREADS_COUNT_SUCCESS, + payload: count, +}); + +export const incrementThreadUnreadCount = () => ({ + type: ReduxActionTypes.INCREMENT_COMMENT_THREAD_UNREAD_COUNT, +}); + +export const decrementThreadUnreadCount = () => ({ + type: ReduxActionTypes.DECREMENT_COMMENT_THREAD_UNREAD_COUNT, +}); diff --git a/app/client/src/actions/datasourceActions.ts b/app/client/src/actions/datasourceActions.ts index 6fa834fd62..65f08d4582 100644 --- a/app/client/src/actions/datasourceActions.ts +++ b/app/client/src/actions/datasourceActions.ts @@ -137,6 +137,29 @@ export const fetchDatasources = () => { }; }; +export const fetchMockDatasources = () => { + return { + type: ReduxActionTypes.FETCH_MOCK_DATASOURCES_INIT, + }; +}; + +export const addMockDatasourceToOrg = ( + name: string, + organizationId: string, + pluginId: string, + packageName: string, +): ReduxAction<{ + name: string; + organizationId: string; + pluginId: string; + packageName: string; +}> => { + return { + type: ReduxActionTypes.ADD_MOCK_DATASOURCES_INIT, + payload: { name, packageName, pluginId, organizationId }, + }; +}; + export const initDatasourcePane = ( pluginType: string, urlId?: string, diff --git a/app/client/src/actions/debuggerActions.ts b/app/client/src/actions/debuggerActions.ts index a0ca54e4a7..ce7beb04cd 100644 --- a/app/client/src/actions/debuggerActions.ts +++ b/app/client/src/actions/debuggerActions.ts @@ -1,5 +1,15 @@ import { ReduxActionTypes } from "constants/ReduxActionConstants"; -import { Message } from "entities/AppsmithConsole"; +import { Message, ENTITY_TYPE } from "entities/AppsmithConsole"; +import { EventName } from "utils/AnalyticsUtil"; + +export interface LogDebuggerErrorAnalyticsPayload { + entityName: string; + entityId: string; + entityType: ENTITY_TYPE; + eventName: EventName; + propertyPath: string; + errorMessages: { message: string }[]; +} export const debuggerLogInit = (payload: Message) => ({ type: ReduxActionTypes.DEBUGGER_LOG_INIT, @@ -29,3 +39,11 @@ export const updateErrorLog = (payload: Message) => ({ type: ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOG, payload, }); + +// Only used for analytics +export const logDebuggerErrorAnalytics = ( + payload: LogDebuggerErrorAnalyticsPayload, +) => ({ + type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS, + payload, +}); diff --git a/app/client/src/actions/notificationActions.ts b/app/client/src/actions/notificationActions.ts new file mode 100644 index 0000000000..209c04504a --- /dev/null +++ b/app/client/src/actions/notificationActions.ts @@ -0,0 +1,58 @@ +import { ReduxActionTypes } from "constants/ReduxActionConstants"; +import { AppsmithNotification } from "entities/Notification"; + +export const fetchNotificationsRequest = (beforeTime?: string) => ({ + type: ReduxActionTypes.FETCH_NOTIFICATIONS_REQUEST, + payload: beforeTime, +}); + +export const fetchNotificationsSuccess = (payload: { + notifications: Array; +}) => ({ + type: ReduxActionTypes.FETCH_NOTIFICATIONS_SUCCESS, + payload, +}); + +export const newNotificationEvent = (payload: Notification) => ({ + type: ReduxActionTypes.NEW_NOTIFICATION_EVENT, + payload, +}); + +export const setIsNotificationsListVisible = (payload: boolean) => ({ + type: ReduxActionTypes.SET_IS_NOTIFICATIONS_LIST_VISIBLE, + payload, +}); + +export const markAllNotificationsAsReadRequest = () => ({ + type: ReduxActionTypes.MARK_ALL_NOTIFICATIONS_AS_READ_REQUEST, +}); + +export const markAllNotificationsAsReadSuccess = () => ({ + type: ReduxActionTypes.MARK_ALL_NOTIFICATIONS_AS_READ_SUCCESS, +}); + +export const resetNotifications = (payload: { + notifications: Array; +}) => ({ + type: ReduxActionTypes.RESET_NOTIFICATIONS, + payload, +}); + +export const fetchUnreadNotificationsCountRequest = () => ({ + type: ReduxActionTypes.FETCH_UNREAD_NOTIFICATIONS_COUNT_REQUEST, +}); + +export const fetchUnreadNotificationsCountSuccess = (payload: number) => ({ + type: ReduxActionTypes.FETCH_UNREAD_NOTIFICATIONS_COUNT_SUCCESS, + payload, +}); + +export const markNotificationAsReadRequest = (payload: string) => ({ + type: ReduxActionTypes.MARK_NOTIFICATION_AS_READ_REQUEST, + payload, +}); + +export const markNotificationAsReadSuccess = (payload: string) => ({ + type: ReduxActionTypes.MARK_NOTIFICATION_AS_READ_SUCCESS, + payload, +}); diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx index f454c0349d..177661dd01 100644 --- a/app/client/src/actions/pageActions.tsx +++ b/app/client/src/actions/pageActions.tsx @@ -30,11 +30,15 @@ export const fetchPageList = ( }; }; -export const fetchPage = (pageId: string): ReduxAction => { +export const fetchPage = ( + pageId: string, + isFirstLoad = false, +): ReduxAction => { return { type: ReduxActionTypes.FETCH_PAGE_INIT, payload: { id: pageId, + isFirstLoad, }, }; }; @@ -47,9 +51,13 @@ export const fetchPublishedPage = (pageId: string, bustCache = false) => ({ }, }); -export const fetchPageSuccess = (): ReduxActionWithoutPayload => { +export const fetchPageSuccess = ( + postEvalActions: Array | ReduxActionWithoutPayload>, +): EvaluationReduxAction => { return { type: ReduxActionTypes.FETCH_PAGE_SUCCESS, + postEvalActions, + payload: undefined, }; }; @@ -75,6 +83,11 @@ export const initCanvasLayout = ( }; }; +export const setLastUpdatedTime = (payload: number): ReduxAction => ({ + type: ReduxActionTypes.SET_LAST_UPDATED_TIME, + payload, +}); + export const savePageSuccess = (payload: SavePageResponse) => { return { type: ReduxActionTypes.SAVE_PAGE_SUCCESS, @@ -104,9 +117,10 @@ export const updateAndSaveLayout = ( }; }; -export const saveLayout = () => { +export const saveLayout = (isRetry?: boolean) => { return { type: ReduxActionTypes.SAVE_PAGE_INIT, + payload: { isRetry }, }; }; @@ -203,6 +217,12 @@ export type WidgetDelete = { isShortcut?: boolean; }; +export type MultipleWidgetDeletePayload = { + widgetIds: string[]; + disallowUndo?: boolean; + isShortcut?: boolean; +}; + export type WidgetResize = { widgetId: string; leftColumn: number; diff --git a/app/client/src/actions/tourActions.ts b/app/client/src/actions/tourActions.ts new file mode 100644 index 0000000000..b32c709a4a --- /dev/null +++ b/app/client/src/actions/tourActions.ts @@ -0,0 +1,22 @@ +import { ReduxActionTypes } from "constants/ReduxActionConstants"; +import { TourType } from "entities/Tour"; + +export const setActiveTour = (tourType: TourType) => ({ + type: ReduxActionTypes.SET_ACTIVE_TOUR, + payload: tourType, +}); + +export const resetActiveTour = () => ({ + type: ReduxActionTypes.RESET_ACTIVE_TOUR, + payload: undefined, +}); + +export const setActiveTourIndex = (index: number) => ({ + type: ReduxActionTypes.SET_ACTIVE_TOUR_INDEX, + payload: index, +}); + +export const proceedToNextTourStep = () => ({ + type: ReduxActionTypes.PROCEED_TO_NEXT_TOUR_STEP, + payload: undefined, +}); diff --git a/app/client/src/actions/userActions.ts b/app/client/src/actions/userActions.ts index 85f1fb7b97..d446a5755c 100644 --- a/app/client/src/actions/userActions.ts +++ b/app/client/src/actions/userActions.ts @@ -65,3 +65,25 @@ export const updateUserDetails = (payload: UpdateUserRequest) => ({ type: ReduxActionTypes.UPDATE_USER_DETAILS_INIT, payload, }); + +export const updatePhoto = (payload: { + file: File; + callback?: () => void; +}) => ({ + type: ReduxActionTypes.UPLOAD_PROFILE_PHOTO, + payload, +}); + +export const removePhoto = (callback: () => void) => ({ + type: ReduxActionTypes.REMOVE_PROFILE_PHOTO, + payload: { callback }, +}); + +export const leaveOrganization = (orgId: string) => { + return { + type: ReduxActionTypes.LEAVE_ORG_INIT, + payload: { + orgId, + }, + }; +}; diff --git a/app/client/src/actions/widgetActions.tsx b/app/client/src/actions/widgetActions.tsx index 03e1f59548..e45f15e366 100644 --- a/app/client/src/actions/widgetActions.tsx +++ b/app/client/src/actions/widgetActions.tsx @@ -63,13 +63,6 @@ export const focusWidget = ( payload: { widgetId }, }); -export const selectWidget = ( - widgetId?: string, -): ReduxAction<{ widgetId?: string }> => ({ - type: ReduxActionTypes.SELECT_WIDGET, - payload: { widgetId }, -}); - export const showModal = (id: string) => { return { type: ReduxActionTypes.SHOW_MODAL, diff --git a/app/client/src/actions/widgetSelectionActions.ts b/app/client/src/actions/widgetSelectionActions.ts new file mode 100644 index 0000000000..c323a53128 --- /dev/null +++ b/app/client/src/actions/widgetSelectionActions.ts @@ -0,0 +1,71 @@ +import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; +import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants"; + +export const selectWidgetAction = ( + widgetId?: string, + isMultiSelect?: boolean, +): ReduxAction<{ widgetId?: string; isMultiSelect?: boolean }> => ({ + type: ReduxActionTypes.SELECT_WIDGET, + payload: { widgetId, isMultiSelect }, +}); + +export const selectWidgetInitAction = ( + widgetId?: string, + isMultiSelect?: boolean, +): ReduxAction<{ widgetId?: string; isMultiSelect?: boolean }> => ({ + type: ReduxActionTypes.SELECT_WIDGET_INIT, + payload: { widgetId, isMultiSelect }, +}); + +export const selectMultipleWidgetsAction = ( + widgetIds?: string[], +): ReduxAction<{ widgetIds?: string[] }> => { + return { + type: ReduxActionTypes.SELECT_MULTIPLE_WIDGETS, + payload: { widgetIds }, + }; +}; + +export const silentAddSelectionsAction = ( + widgetIds?: string[], +): ReduxAction<{ widgetIds?: string[] }> => { + return { + type: ReduxActionTypes.SELECT_WIDGETS, + payload: { widgetIds }, + }; +}; + +export const deselectMultipleWidgetsAction = ( + widgetIds?: string[], +): ReduxAction<{ widgetIds?: string[] }> => { + return { + type: ReduxActionTypes.DESELECT_WIDGETS, + payload: { widgetIds }, + }; +}; + +export const selectAllWidgetsInCanvasInitAction = ( + canvasId = MAIN_CONTAINER_WIDGET_ID, +): ReduxAction<{ canvasId: string }> => { + return { + type: ReduxActionTypes.SELECT_ALL_WIDGETS_IN_CANVAS_INIT, + payload: { + canvasId, + }, + }; +}; + +export const selectMultipleWidgetsInitAction = (widgetIds: string[]) => { + return { + type: ReduxActionTypes.SELECT_MULTIPLE_WIDGETS_INIT, + payload: { widgetIds }, + }; +}; + +export const shiftSelectWidgetsEntityExplorerInitAction = ( + widgetId: string, + siblingWidgets: string[], +): ReduxAction<{ widgetId: string; siblingWidgets: string[] }> => ({ + type: ReduxActionTypes.SHIFT_SELECT_WIDGET_INIT, + payload: { widgetId, siblingWidgets }, +}); diff --git a/app/client/src/api/Api.ts b/app/client/src/api/Api.ts index bc1cdfbd2a..32dfc0f65c 100644 --- a/app/client/src/api/Api.ts +++ b/app/client/src/api/Api.ts @@ -68,6 +68,22 @@ class Api { ); } + static patch( + url: string, + body?: any, + queryParams?: any, + config: Partial = {}, + ) { + return axiosInstance.patch( + url + convertObjectToQueryParams(queryParams), + body, + { + ...apiRequestConfig, + ...config, + }, + ); + } + static delete( url: string, queryParams?: any, @@ -80,6 +96,6 @@ class Api { } } -export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"; +export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; export default Api; diff --git a/app/client/src/api/ApiUtils.ts b/app/client/src/api/ApiUtils.ts index 2da9b22cf9..81f94cca20 100644 --- a/app/client/src/api/ApiUtils.ts +++ b/app/client/src/api/ApiUtils.ts @@ -10,10 +10,11 @@ import { ERROR_CODES, SERVER_ERROR_CODES, } from "constants/ApiConstants"; -import history from "utils/history"; -import { AUTH_LOGIN_URL } from "constants/routes"; import log from "loglevel"; import { ActionExecutionResponse } from "api/ActionAPI"; +import store from "store"; +import { logoutUser } from "actions/userActions"; +import { AUTH_LOGIN_URL } from "constants/routes"; const executeActionRegex = /actions\/execute/; const timeoutErrorRegex = /timeout of (\d+)ms exceeded/; @@ -100,10 +101,13 @@ export const apiFailureResponseInterceptor = (error: any) => { const currentUrl = `${window.location.href}`; if (error.response.status === API_STATUS_CODES.REQUEST_NOT_AUTHORISED) { // Redirect to login and set a redirect url. - history.replace({ - pathname: AUTH_LOGIN_URL, - search: `redirectUrl=${currentUrl}`, - }); + store.dispatch( + logoutUser({ + redirectURL: `${AUTH_LOGIN_URL}?redirectUrl=${encodeURIComponent( + currentUrl, + )}`, + }), + ); return Promise.reject({ code: ERROR_CODES.REQUEST_NOT_AUTHORISED, message: "Unauthorized. Redirecting to login page...", diff --git a/app/client/src/api/ApplicationApi.tsx b/app/client/src/api/ApplicationApi.tsx index 0347802ff0..21349820f9 100644 --- a/app/client/src/api/ApplicationApi.tsx +++ b/app/client/src/api/ApplicationApi.tsx @@ -31,11 +31,12 @@ export interface ApplicationResponsePayload { pages?: ApplicationPagePayload[]; appIsExample: boolean; appLayout?: AppLayoutConfig; + unreadCommentThreads?: number; } -// export interface FetchApplicationResponse extends ApiResponse { -// data: ApplicationResponsePayload & { pages: ApplicationPagePayload[] }; -// } +export interface FetchApplicationResponse extends ApiResponse { + data: ApplicationResponsePayload & { pages: ApplicationPagePayload[] }; +} export interface FetchApplicationsResponse extends ApiResponse { data: Array; @@ -119,6 +120,13 @@ export interface FetchUsersApplicationsOrgsResponse extends ApiResponse { }; } +export interface ImportApplicationRequest { + orgId: string; + applicationFile?: File; + progress?: (progressEvent: ProgressEvent) => void; + onSuccessCallback?: () => void; +} + class ApplicationApi extends Api { static baseURL = "v1/applications/"; static publishURLPath = (applicationId: string) => `publish/${applicationId}`; @@ -147,13 +155,13 @@ class ApplicationApi extends Api { static fetchApplication( applicationId: string, - ): AxiosPromise { + ): AxiosPromise { return Api.get(ApplicationApi.baseURL + applicationId); } static fetchApplicationForViewMode( applicationId: string, - ): AxiosPromise { + ): AxiosPromise { return Api.get(ApplicationApi.baseURL + `view/${applicationId}`); } @@ -212,6 +220,21 @@ class ApplicationApi extends Api { request.organizationId, ); } + + static importApplicationToOrg( + request: ImportApplicationRequest, + ): AxiosPromise { + const formData = new FormData(); + if (request.applicationFile) { + formData.append("file", request.applicationFile); + } + return Api.post("v1/applications/import/" + request.orgId, formData, null, { + headers: { + "Content-Type": "multipart/form-data", + }, + onUploadProgress: request.progress, + }); + } } export default ApplicationApi; diff --git a/app/client/src/api/CommentsAPI.tsx b/app/client/src/api/CommentsAPI.tsx index ce9fdf00d3..f4efd9307a 100644 --- a/app/client/src/api/CommentsAPI.tsx +++ b/app/client/src/api/CommentsAPI.tsx @@ -10,6 +10,8 @@ class CommentsApi extends Api { static baseURL = "v1/comments"; static getThreadsAPI = `${CommentsApi.baseURL}/threads`; static getCommentsAPI = CommentsApi.baseURL; + static getReactionsAPI = (commentId: string) => + `${CommentsApi.getCommentsAPI}/${commentId}/reactions`; static createNewThread( request: CreateCommentThreadRequest, @@ -33,23 +35,48 @@ class CommentsApi extends Api { } static updateCommentThread( - updateCommentRequest: Partial, + updateCommentThreadRequest: Partial, threadId: string, ): AxiosPromise { return Api.put( `${CommentsApi.getThreadsAPI}/${threadId}`, - updateCommentRequest, + updateCommentThreadRequest, ); } - static pinCommentThread(threadId: string) { - console.log(threadId); - return Promise.resolve(); + static updateComment( + updateCommentRequest: Partial, + commentId: string, + ): AxiosPromise { + return Api.put( + `${CommentsApi.getCommentsAPI}/${commentId}`, + updateCommentRequest, + ); } static deleteComment(commentId: string): AxiosPromise { return Api.delete(`${CommentsApi.getCommentsAPI}/${commentId}`); } + + static deleteCommentThread(threadId: string): AxiosPromise { + return Api.delete(`${CommentsApi.getThreadsAPI}/${threadId}`); + } + + static addCommentReaction( + commentId: string, + request: { emoji: string }, + ): AxiosPromise { + return Api.post(CommentsApi.getReactionsAPI(commentId), request); + } + + static removeCommentReaction( + commentId: string, + request: { emoji: string }, + ): AxiosPromise { + return Api.delete(CommentsApi.getReactionsAPI(commentId), null, { + data: request, + }); + } } export default CommentsApi; diff --git a/app/client/src/api/DatasourcesApi.ts b/app/client/src/api/DatasourcesApi.ts index f879711775..8b15c02be8 100644 --- a/app/client/src/api/DatasourcesApi.ts +++ b/app/client/src/api/DatasourcesApi.ts @@ -63,6 +63,26 @@ class DatasourcesApi extends API { DatasourcesApi.url + `/${id}/structure?ignoreCache=${ignoreCache}`, ); } + + static fetchMockDatasources(): AxiosPromise< + GenericApiResponse + > { + return API.get(DatasourcesApi.url + "/mocks"); + } + + static addMockDbToDatasources( + name: string, + organizationId: string, + pluginId: string, + packageName: string, + ): Promise { + return API.post(DatasourcesApi.url + `/mocks`, { + name, + organizationId, + pluginId, + packageName, + }); + } } export default DatasourcesApi; diff --git a/app/client/src/api/ImportApi.ts b/app/client/src/api/ImportApi.ts index 310d4c5515..eac6e0ad76 100644 --- a/app/client/src/api/ImportApi.ts +++ b/app/client/src/api/ImportApi.ts @@ -14,7 +14,7 @@ class CurlImportApi extends Api { static curlImportURL = `v1/import`; static curlImport(request: CurlImportRequest): AxiosPromise { - const { pageId, name, curl, organizationId } = request; + const { curl, name, organizationId, pageId } = request; return Api.post(CurlImportApi.curlImportURL, curl, { type: "CURL", pageId, diff --git a/app/client/src/api/NotificationsAPI.tsx b/app/client/src/api/NotificationsAPI.tsx new file mode 100644 index 0000000000..51abaacab7 --- /dev/null +++ b/app/client/src/api/NotificationsAPI.tsx @@ -0,0 +1,33 @@ +import { AxiosPromise } from "axios"; +import Api from "./Api"; +import { ApiResponse } from "./ApiResponses"; + +class NotificationsApi extends Api { + static baseURL = "v1/notifications"; + static markAsReadURL = `${NotificationsApi.baseURL}/isRead`; + static markAllAsReadURL = `${NotificationsApi.markAsReadURL}/all`; + static fetchUnreadNotificationsCountURL = `${NotificationsApi.baseURL}/count/unread`; + + static fetchNotifications(beforeDate?: string): AxiosPromise { + return Api.get(NotificationsApi.baseURL, beforeDate ? { beforeDate } : {}); + } + + static markAllNotificationsAsRead(): AxiosPromise { + return Api.patch(NotificationsApi.markAllAsReadURL, { isRead: true }); + } + + static fetchUnreadNotificationsCount(): AxiosPromise { + return Api.get(NotificationsApi.fetchUnreadNotificationsCountURL); + } + + static markNotificationsAsRead( + ids: Array, + ): AxiosPromise { + return Api.patch(NotificationsApi.markAsReadURL, { + isRead: true, + idList: ids, + }); + } +} + +export default NotificationsApi; diff --git a/app/client/src/api/PageApi.tsx b/app/client/src/api/PageApi.tsx index fa229d750c..fdc931d00a 100644 --- a/app/client/src/api/PageApi.tsx +++ b/app/client/src/api/PageApi.tsx @@ -7,6 +7,7 @@ import { PageAction } from "constants/AppsmithActionConstants/ActionConstants"; export interface FetchPageRequest { id: string; + isFirstLoad?: boolean; } export interface FetchPublishedPageRequest { diff --git a/app/client/src/api/UserApi.tsx b/app/client/src/api/UserApi.tsx index 72bdb2b5a8..827dc2f977 100644 --- a/app/client/src/api/UserApi.tsx +++ b/app/client/src/api/UserApi.tsx @@ -40,6 +40,10 @@ export interface FetchUserRequest { id: string; } +export interface LeaveOrgRequest { + orgId: string; +} + export interface InviteUserRequest { email: string; groupIds: string[]; @@ -47,7 +51,8 @@ export interface InviteUserRequest { } export interface UpdateUserRequest { - name: string; + name?: string; + email?: string; } class UserApi extends Api { @@ -59,8 +64,10 @@ class UserApi extends Api { static verifyInviteTokenURL = `${UserApi.inviteUserURL}/verify`; static confirmUserInviteURL = `${UserApi.inviteUserURL}/confirm`; static addOrgURL = `${UserApi.usersURL}/addOrganization`; + static leaveOrgURL = `${UserApi.usersURL}/leaveOrganization`; static logoutURL = "v1/logout"; static currentUserURL = "v1/users/me"; + static photoURL = "v1/users/photo"; static createUser( request: CreateUserRequest, @@ -117,6 +124,27 @@ class UserApi extends Api { static logoutUser(): AxiosPromise { return Api.post(UserApi.logoutURL); } + + static uploadPhoto(request: { file: File }): AxiosPromise { + const formData = new FormData(); + if (request.file) { + formData.append("file", request.file); + } + + return Api.post(UserApi.photoURL, formData, null, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + } + + static deletePhoto(): AxiosPromise { + return Api.delete(UserApi.photoURL); + } + + static leaveOrg(request: LeaveOrgRequest): AxiosPromise { + return Api.put(UserApi.leaveOrgURL + "/" + request.orgId); + } } export default UserApi; diff --git a/app/client/src/assets/icons/ads/app-icons/draghandler.svg b/app/client/src/assets/icons/ads/app-icons/draghandler.svg index 133b2b6bdb..2b3f4cc694 100644 --- a/app/client/src/assets/icons/ads/app-icons/draghandler.svg +++ b/app/client/src/assets/icons/ads/app-icons/draghandler.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/ads/bell.svg b/app/client/src/assets/icons/ads/bell.svg new file mode 100644 index 0000000000..e4a36d411c --- /dev/null +++ b/app/client/src/assets/icons/ads/bell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/ads/bug.svg b/app/client/src/assets/icons/ads/bug.svg index 28f60f75af..43864e8a92 100644 --- a/app/client/src/assets/icons/ads/bug.svg +++ b/app/client/src/assets/icons/ads/bug.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/ads/cancel.svg b/app/client/src/assets/icons/ads/cancel.svg index 254210469e..d397aa2f65 100644 --- a/app/client/src/assets/icons/ads/cancel.svg +++ b/app/client/src/assets/icons/ads/cancel.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/ads/card-context-menu.svg b/app/client/src/assets/icons/ads/card-context-menu.svg index eadc7a9738..e3fe0a28e8 100644 --- a/app/client/src/assets/icons/ads/card-context-menu.svg +++ b/app/client/src/assets/icons/ads/card-context-menu.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/ads/commentIcon.png b/app/client/src/assets/icons/ads/commentIcon.png index 2775b7b58d..e6d22c7d5e 100644 Binary files a/app/client/src/assets/icons/ads/commentIcon.png and b/app/client/src/assets/icons/ads/commentIcon.png differ diff --git a/app/client/src/assets/icons/ads/cross.svg b/app/client/src/assets/icons/ads/cross.svg index 9589ed4d54..ac7eaaca70 100644 --- a/app/client/src/assets/icons/ads/cross.svg +++ b/app/client/src/assets/icons/ads/cross.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/ads/download.svg b/app/client/src/assets/icons/ads/download.svg new file mode 100644 index 0000000000..230d2ec50d --- /dev/null +++ b/app/client/src/assets/icons/ads/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/ads/expand-more.svg b/app/client/src/assets/icons/ads/expand-more.svg new file mode 100644 index 0000000000..41a5c0133c --- /dev/null +++ b/app/client/src/assets/icons/ads/expand-more.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/ads/open.svg b/app/client/src/assets/icons/ads/open.svg index 2b43521e49..1ca1dd170b 100644 --- a/app/client/src/assets/icons/ads/open.svg +++ b/app/client/src/assets/icons/ads/open.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/ads/trending-flat.svg b/app/client/src/assets/icons/ads/trending-flat.svg new file mode 100644 index 0000000000..01b641efcd --- /dev/null +++ b/app/client/src/assets/icons/ads/trending-flat.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/ads/upload_success.svg b/app/client/src/assets/icons/ads/upload_success.svg new file mode 100644 index 0000000000..20a260c740 --- /dev/null +++ b/app/client/src/assets/icons/ads/upload_success.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/ads/wand.svg b/app/client/src/assets/icons/ads/wand.svg index 5a51055344..824e0a3545 100644 --- a/app/client/src/assets/icons/ads/wand.svg +++ b/app/client/src/assets/icons/ads/wand.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/ads/warning-triangle.svg b/app/client/src/assets/icons/ads/warning-triangle.svg index 350e33a327..c4807dff5c 100644 --- a/app/client/src/assets/icons/ads/warning-triangle.svg +++ b/app/client/src/assets/icons/ads/warning-triangle.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/chat.svg b/app/client/src/assets/icons/comments/chat.svg new file mode 100644 index 0000000000..7bb3a13ea4 --- /dev/null +++ b/app/client/src/assets/icons/comments/chat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/check-oval.svg b/app/client/src/assets/icons/comments/check-oval.svg index 13fcb0032a..8abc88b134 100644 --- a/app/client/src/assets/icons/comments/check-oval.svg +++ b/app/client/src/assets/icons/comments/check-oval.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/comment-mode-unread-indicator.svg b/app/client/src/assets/icons/comments/comment-mode-unread-indicator.svg new file mode 100644 index 0000000000..6462f9cb80 --- /dev/null +++ b/app/client/src/assets/icons/comments/comment-mode-unread-indicator.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/commentCursor.png b/app/client/src/assets/icons/comments/commentCursor.png new file mode 100644 index 0000000000..25adce859f Binary files /dev/null and b/app/client/src/assets/icons/comments/commentCursor.png differ diff --git a/app/client/src/assets/icons/comments/context-menu.svg b/app/client/src/assets/icons/comments/context-menu.svg new file mode 100644 index 0000000000..646ae34044 --- /dev/null +++ b/app/client/src/assets/icons/comments/context-menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/down-arrow.svg b/app/client/src/assets/icons/comments/down-arrow.svg new file mode 100644 index 0000000000..6274f608f9 --- /dev/null +++ b/app/client/src/assets/icons/comments/down-arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/edit-mode.svg b/app/client/src/assets/icons/comments/edit-mode.svg new file mode 100644 index 0000000000..5aea2a20fa --- /dev/null +++ b/app/client/src/assets/icons/comments/edit-mode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/emoji.svg b/app/client/src/assets/icons/comments/emoji.svg index bc123ca1a0..9337c2dede 100644 --- a/app/client/src/assets/icons/comments/emoji.svg +++ b/app/client/src/assets/icons/comments/emoji.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/eye.svg b/app/client/src/assets/icons/comments/eye.svg new file mode 100644 index 0000000000..987bf4e7c2 --- /dev/null +++ b/app/client/src/assets/icons/comments/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/filter.svg b/app/client/src/assets/icons/comments/filter.svg new file mode 100644 index 0000000000..5d4f05f9de --- /dev/null +++ b/app/client/src/assets/icons/comments/filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/link.svg b/app/client/src/assets/icons/comments/link.svg index b0f4250ef4..72f68c513b 100644 --- a/app/client/src/assets/icons/comments/link.svg +++ b/app/client/src/assets/icons/comments/link.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/notifications-empty-state.svg b/app/client/src/assets/icons/comments/notifications-empty-state.svg new file mode 100644 index 0000000000..cbce95b8ec --- /dev/null +++ b/app/client/src/assets/icons/comments/notifications-empty-state.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/pen.svg b/app/client/src/assets/icons/comments/pen.svg new file mode 100644 index 0000000000..80d650514f --- /dev/null +++ b/app/client/src/assets/icons/comments/pen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/pin.svg b/app/client/src/assets/icons/comments/pin.svg index 5ba851dbdc..60301bb220 100644 --- a/app/client/src/assets/icons/comments/pin.svg +++ b/app/client/src/assets/icons/comments/pin.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/pin_2.svg b/app/client/src/assets/icons/comments/pin_2.svg index d5ca8f9991..d7d1825d40 100644 --- a/app/client/src/assets/icons/comments/pin_2.svg +++ b/app/client/src/assets/icons/comments/pin_2.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/pin_3.svg b/app/client/src/assets/icons/comments/pin_3.svg new file mode 100644 index 0000000000..59101bf219 --- /dev/null +++ b/app/client/src/assets/icons/comments/pin_3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/reaction-2.svg b/app/client/src/assets/icons/comments/reaction-2.svg new file mode 100644 index 0000000000..91b9cce1af --- /dev/null +++ b/app/client/src/assets/icons/comments/reaction-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/reaction.svg b/app/client/src/assets/icons/comments/reaction.svg new file mode 100644 index 0000000000..433c705a35 --- /dev/null +++ b/app/client/src/assets/icons/comments/reaction.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/read-pin.svg b/app/client/src/assets/icons/comments/read-pin.svg new file mode 100644 index 0000000000..24d0fa35eb --- /dev/null +++ b/app/client/src/assets/icons/comments/read-pin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/send-button.svg b/app/client/src/assets/icons/comments/send-button.svg index c524e029bd..246664a879 100644 --- a/app/client/src/assets/icons/comments/send-button.svg +++ b/app/client/src/assets/icons/comments/send-button.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/toggle-comment-mode.svg b/app/client/src/assets/icons/comments/toggle-comment-mode.svg index 9df15abe13..7e6bcd4849 100644 --- a/app/client/src/assets/icons/comments/toggle-comment-mode.svg +++ b/app/client/src/assets/icons/comments/toggle-comment-mode.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/trash.svg b/app/client/src/assets/icons/comments/trash.svg index b67db540e0..0d0cd882fc 100644 --- a/app/client/src/assets/icons/comments/trash.svg +++ b/app/client/src/assets/icons/comments/trash.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/unpin.svg b/app/client/src/assets/icons/comments/unpin.svg new file mode 100644 index 0000000000..0f1c9866c7 --- /dev/null +++ b/app/client/src/assets/icons/comments/unpin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/comments/unread-pin.svg b/app/client/src/assets/icons/comments/unread-pin.svg new file mode 100644 index 0000000000..5b731a24e0 --- /dev/null +++ b/app/client/src/assets/icons/comments/unread-pin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/arrow_forward.svg b/app/client/src/assets/icons/control/arrow_forward.svg new file mode 100644 index 0000000000..70344d66f5 --- /dev/null +++ b/app/client/src/assets/icons/control/arrow_forward.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/back.svg b/app/client/src/assets/icons/control/back.svg index 3c600a22a9..b34278d7d9 100644 --- a/app/client/src/assets/icons/control/back.svg +++ b/app/client/src/assets/icons/control/back.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/cap_dot.svg b/app/client/src/assets/icons/control/cap_dot.svg new file mode 100644 index 0000000000..9dc534c5ee --- /dev/null +++ b/app/client/src/assets/icons/control/cap_dot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/cap_solid.svg b/app/client/src/assets/icons/control/cap_solid.svg new file mode 100644 index 0000000000..b3c4759250 --- /dev/null +++ b/app/client/src/assets/icons/control/cap_solid.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/copy2.svg b/app/client/src/assets/icons/control/copy2.svg new file mode 100644 index 0000000000..f07eade443 --- /dev/null +++ b/app/client/src/assets/icons/control/copy2.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/control/cut.svg b/app/client/src/assets/icons/control/cut.svg new file mode 100644 index 0000000000..24d44a1945 --- /dev/null +++ b/app/client/src/assets/icons/control/cut.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/control/divider_cap_all.svg b/app/client/src/assets/icons/control/divider_cap_all.svg new file mode 100644 index 0000000000..e854c51bab --- /dev/null +++ b/app/client/src/assets/icons/control/divider_cap_all.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/divider_cap_left.svg b/app/client/src/assets/icons/control/divider_cap_left.svg new file mode 100644 index 0000000000..820d7fc5cb --- /dev/null +++ b/app/client/src/assets/icons/control/divider_cap_left.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/divider_cap_right.svg b/app/client/src/assets/icons/control/divider_cap_right.svg new file mode 100644 index 0000000000..ffa6105c39 --- /dev/null +++ b/app/client/src/assets/icons/control/divider_cap_right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/download-data-icon.svg b/app/client/src/assets/icons/control/download-data-icon.svg new file mode 100644 index 0000000000..eb400b1f56 --- /dev/null +++ b/app/client/src/assets/icons/control/download-data-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/download-table.svg b/app/client/src/assets/icons/control/download-table.svg deleted file mode 100755 index 129ee6b5ad..0000000000 --- a/app/client/src/assets/icons/control/download-table.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/client/src/assets/icons/control/filter-icon.svg b/app/client/src/assets/icons/control/filter-icon.svg old mode 100755 new mode 100644 index 1723d3efe8..4d92889575 --- a/app/client/src/assets/icons/control/filter-icon.svg +++ b/app/client/src/assets/icons/control/filter-icon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/group.svg b/app/client/src/assets/icons/control/group.svg new file mode 100644 index 0000000000..03b7eea423 --- /dev/null +++ b/app/client/src/assets/icons/control/group.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/control/line_dashed.svg b/app/client/src/assets/icons/control/line_dashed.svg new file mode 100644 index 0000000000..34e76b522a --- /dev/null +++ b/app/client/src/assets/icons/control/line_dashed.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/line_dotted.svg b/app/client/src/assets/icons/control/line_dotted.svg new file mode 100644 index 0000000000..99693fd967 --- /dev/null +++ b/app/client/src/assets/icons/control/line_dotted.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/control/open-new-tab.svg b/app/client/src/assets/icons/control/open-new-tab.svg index 05d3815c3c..2cf241d021 100644 --- a/app/client/src/assets/icons/control/open-new-tab.svg +++ b/app/client/src/assets/icons/control/open-new-tab.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/menu/binding.svg b/app/client/src/assets/icons/menu/binding.svg new file mode 100644 index 0000000000..22692a0acd --- /dev/null +++ b/app/client/src/assets/icons/menu/binding.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/client/src/assets/icons/menu/new-plus.svg b/app/client/src/assets/icons/menu/new-plus.svg new file mode 100644 index 0000000000..5016558969 --- /dev/null +++ b/app/client/src/assets/icons/menu/new-plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/widget/divider.svg b/app/client/src/assets/icons/widget/divider.svg new file mode 100644 index 0000000000..e3d413a99b --- /dev/null +++ b/app/client/src/assets/icons/widget/divider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/widget/embed.svg b/app/client/src/assets/icons/widget/embed.svg new file mode 100755 index 0000000000..f56a358be2 --- /dev/null +++ b/app/client/src/assets/icons/widget/embed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/icons/widget/list.svg b/app/client/src/assets/icons/widget/list.svg index 87348a0e9e..4e26df872f 100644 --- a/app/client/src/assets/icons/widget/list.svg +++ b/app/client/src/assets/icons/widget/list.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/app/client/src/assets/icons/widget/rating.svg b/app/client/src/assets/icons/widget/rating.svg new file mode 100644 index 0000000000..4b3e80c619 --- /dev/null +++ b/app/client/src/assets/icons/widget/rating.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/client/src/assets/images/Curl-logo.svg b/app/client/src/assets/images/Curl-logo.svg index 08f86ba19f..fa01f7bca6 100644 --- a/app/client/src/assets/images/Curl-logo.svg +++ b/app/client/src/assets/images/Curl-logo.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/app/client/src/assets/images/InspectElement.svg b/app/client/src/assets/images/InspectElement.svg new file mode 100644 index 0000000000..7c74deff90 --- /dev/null +++ b/app/client/src/assets/images/InspectElement.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/images/Plus-logo.svg b/app/client/src/assets/images/Plus-logo.svg new file mode 100644 index 0000000000..2116267a8f --- /dev/null +++ b/app/client/src/assets/images/Plus-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/images/comments-onboarding/step-1.png b/app/client/src/assets/images/comments-onboarding/step-1.png new file mode 100644 index 0000000000..961bea1a83 Binary files /dev/null and b/app/client/src/assets/images/comments-onboarding/step-1.png differ diff --git a/app/client/src/assets/images/comments-onboarding/step-2.png b/app/client/src/assets/images/comments-onboarding/step-2.png new file mode 100644 index 0000000000..47bbf9a563 Binary files /dev/null and b/app/client/src/assets/images/comments-onboarding/step-2.png differ diff --git a/app/client/src/assets/images/comments-onboarding/step-3.png b/app/client/src/assets/images/comments-onboarding/step-3.png new file mode 100644 index 0000000000..3f1db4eea7 Binary files /dev/null and b/app/client/src/assets/images/comments-onboarding/step-3.png differ diff --git a/app/client/src/assets/images/comments-onboarding/step-4.png b/app/client/src/assets/images/comments-onboarding/step-4.png new file mode 100644 index 0000000000..6afb39482c Binary files /dev/null and b/app/client/src/assets/images/comments-onboarding/step-4.png differ diff --git a/app/client/src/assets/images/comments-onboarding/step-5.png b/app/client/src/assets/images/comments-onboarding/step-5.png new file mode 100644 index 0000000000..3b14415efa Binary files /dev/null and b/app/client/src/assets/images/comments-onboarding/step-5.png differ diff --git a/app/client/src/assets/images/profile-placeholder.svg b/app/client/src/assets/images/profile-placeholder.svg new file mode 100644 index 0000000000..b8ef8acc18 --- /dev/null +++ b/app/client/src/assets/images/profile-placeholder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/assets/lottie/pulse-dot.json b/app/client/src/assets/lottie/pulse-dot.json new file mode 100644 index 0000000000..a4901959e1 --- /dev/null +++ b/app/client/src/assets/lottie/pulse-dot.json @@ -0,0 +1 @@ +{"v":"5.5.0","fr":29.9700012207031,"ip":0,"op":70.0000028511585,"w":250,"h":250,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":90,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[118.549,117.468,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,2.889]},"t":0,"s":[83.62,83.62,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,-0.549]},"t":27,"s":[105.62,105.62,100]},{"t":52.0000021180034,"s":[83.62,83.62,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[106.672,106.672],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9490196078431372,0.16862745098039217,0.16862745098039217,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[7.715,9.008],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":72.0000029326201,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"w1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":25.0000010182709,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[125.156,124.494,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[83.6,83.6,100]},{"t":25.0000010182709,"s":[234.048,234.048,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[69.508,69.508],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9490196078431372,0.16862745098039217,0.16862745098039217,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-0.187,0.605],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[139.443,139.443],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"w2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":30.0000012219251,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[125.326,124.285,0],"ix":2},"a":{"a":0,"k":[-44.248,-66.371,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[92.661,92.661,100]},{"t":30.0000012219251,"s":[229.892,229.892,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[86.801,86.801],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9490196078431372,0.16862745098039217,0.16862745098039217,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-44.6,-65.6],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/client/src/comments/AppComments/AppCommentThreads.tsx b/app/client/src/comments/AppComments/AppCommentThreads.tsx index d15727fe6b..e3fc2d4036 100644 --- a/app/client/src/comments/AppComments/AppCommentThreads.tsx +++ b/app/client/src/comments/AppComments/AppCommentThreads.tsx @@ -1,14 +1,29 @@ import React, { useMemo } from "react"; +import { useSelector } from "react-redux"; +import styled from "styled-components"; + import { - getSortedAppCommentThreadIds, + getSortedAndFilteredAppCommentThreadIds, applicationCommentsSelector, allCommentThreadsMap, getAppCommentThreads, + shouldShowResolved as shouldShowResolvedSelector, + appCommentsFilter as appCommentsFilterSelector, } from "selectors/commentsSelectors"; import { getCurrentApplicationId } from "selectors/editorSelectors"; -import { useSelector } from "react-redux"; import CommentThread from "comments/CommentThread/connectedCommentThread"; +import AppCommentsPlaceholder from "./AppCommentsPlaceholder"; +import { getCurrentUser } from "selectors/usersSelectors"; + +import { Virtuoso } from "react-virtuoso"; + +const Container = styled.div` + display: flex; + flex-direction: column; + flex: 1; + overflow: auto; +`; function AppCommentThreads() { const applicationId = useSelector(getCurrentApplicationId) as string; @@ -17,23 +32,53 @@ function AppCommentThreads() { ); const appCommentThreadIds = getAppCommentThreads(appCommentThreadsByRefMap); const commentThreadsMap = useSelector(allCommentThreadsMap); + const shouldShowResolved = useSelector(shouldShowResolvedSelector); + const appCommentsFilter = useSelector(appCommentsFilterSelector); + + const currentUser = useSelector(getCurrentUser); + const currentUsername = currentUser?.username; const commentThreadIds = useMemo( - () => getSortedAppCommentThreadIds(appCommentThreadIds, commentThreadsMap), - [appCommentThreadIds, commentThreadsMap], + () => + getSortedAndFilteredAppCommentThreadIds( + appCommentThreadIds, + commentThreadsMap, + shouldShowResolved, + appCommentsFilter, + currentUsername, + ), + [ + appCommentThreadIds, + commentThreadsMap, + shouldShowResolved, + appCommentsFilter, + currentUsername, + ], ); return ( - // eslint-disable-next-line react/jsx-no-useless-fragment - <> - {commentThreadIds?.map((commentThreadId: string) => ( - + {commentThreadIds.length > 0 && ( + ( + /** Keeping this as a fail safe: since zero + * height elements throw an error + * */ +
+ +
+ )} /> - ))} - + )} + {commentThreadIds.length === 0 && } + ); } diff --git a/app/client/src/comments/AppComments/AppComments.tsx b/app/client/src/comments/AppComments/AppComments.tsx index 5791a54a62..c78e9df639 100644 --- a/app/client/src/comments/AppComments/AppComments.tsx +++ b/app/client/src/comments/AppComments/AppComments.tsx @@ -1,28 +1,19 @@ -import React, { useState } from "react"; +import React from "react"; import { useSelector } from "react-redux"; import { commentModeSelector } from "selectors/commentsSelectors"; import AppCommentsHeader from "./AppCommentsHeader"; -import AppCommentThreads from "./AppCommentThreadsContainer"; +import AppCommentThreads from "./AppCommentThreads"; import Container from "./Container"; -import { useCallback } from "react"; -function AppComments() { - const [isOpen, setIsOpen] = useState(false); +function AppComments(props: { isInline?: boolean }) { const isCommentMode = useSelector(commentModeSelector); - const onClose = useCallback(() => { - setIsOpen(!isOpen); - }, [isOpen]); if (!isCommentMode) return null; return ( - - - + + + ); } diff --git a/app/client/src/comments/AppComments/AppCommentsFilterPopover.tsx b/app/client/src/comments/AppComments/AppCommentsFilterPopover.tsx new file mode 100644 index 0000000000..f3a4fe2466 --- /dev/null +++ b/app/client/src/comments/AppComments/AppCommentsFilterPopover.tsx @@ -0,0 +1,114 @@ +import React, { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import styled, { withTheme } from "styled-components"; +import Icon, { IconSize } from "components/ads/Icon"; +import { Popover2 } from "@blueprintjs/popover2"; +import RadioGroup, { Radio } from "components/ads/Radio"; +import { Theme } from "constants/DefaultTheme"; +import Checkbox from "components/ads/Checkbox"; + +import { + shouldShowResolved as shouldShowResolvedSelector, + appCommentsFilter as appCommentsFilterSelector, +} from "selectors/commentsSelectors"; +import { + setShouldShowResolvedComments, + setAppCommentsFilter, +} from "actions/commentActions"; + +import "@blueprintjs/popover2/lib/css/blueprint-popover2.css"; + +export const options = [ + { label: "Show all comments", value: "show-all" }, + { label: "Show only pinned", value: "show-only-pinned" }, + { label: "Show only yours", value: "show-only-yours" }, +]; + +const checkboxes = [ + { label: "Show resolved comments", value: "show-resolved" }, +]; + +const Container = styled.div` + width: 180px; + padding: ${(props) => props.theme.spaces[5]}px; + && ${Radio} { + margin-bottom: ${(props) => props.theme.spaces[6]}px; + } +`; + +const Row = styled.div` + margin-bottom: ${(props) => props.theme.spaces[6]}px; + &:last-child { + margin-bottom: 0; + } +`; + +const useSetResolvedFilterFromQuery = () => { + const dispatch = useDispatch(); + + useEffect(() => { + const url = new URL(window.location.href); + const searchParams = url.searchParams; + console.log(window.location.href, "window.location.href"); + if (searchParams.get("isResolved")) { + dispatch(setShouldShowResolvedComments(true)); + } + }, []); +}; + +const AppCommentsFilter = withTheme(({ theme }: { theme: Theme }) => { + const dispatch = useDispatch(); + const shouldShowResolved = useSelector(shouldShowResolvedSelector); + const appCommentsFilter = useSelector(appCommentsFilterSelector); + + return ( + + dispatch(setAppCommentsFilter(value))} + options={options} + rows={3} + /> + {checkboxes.map(({ label }) => ( + + + dispatch(setShouldShowResolvedComments(isChecked)) + } + /> + + ))} + + ); +}); + +function AppCommentsFilterPopover() { + useSetResolvedFilterFromQuery(); + + return ( + } + modifiers={{ + offset: { + enabled: true, + options: { + offset: [7, 10], + }, + }, + preventOverflow: { + enabled: true, + }, + }} + placement={"bottom-end"} + portalClassName="comment-context-menu" + > + + + ); +} + +export default AppCommentsFilterPopover; diff --git a/app/client/src/comments/AppComments/AppCommentsHeader.tsx b/app/client/src/comments/AppComments/AppCommentsHeader.tsx index 5fb37c55a6..d6b12c76b6 100644 --- a/app/client/src/comments/AppComments/AppCommentsHeader.tsx +++ b/app/client/src/comments/AppComments/AppCommentsHeader.tsx @@ -1,49 +1,32 @@ -import React, { useCallback } from "react"; -import styled, { withTheme } from "styled-components"; -import { getTypographyByKey, Theme } from "constants/DefaultTheme"; +import React from "react"; +import styled from "styled-components"; +import { getTypographyByKey } from "constants/DefaultTheme"; import { COMMENTS, createMessage } from "constants/messages"; -import Icon from "components/ads/Icon"; + +import AppCommentsFilterPopover from "./AppCommentsFilterPopover"; const AppCommentHeaderTitle = styled.div` color: ${(props) => props.theme.colors.comments.appCommentsHeaderTitle}; - ${(props) => getTypographyByKey(props, "h2")} + ${(props) => getTypographyByKey(props, "h5")} `; -const Header = styled.div<{ isOpen: boolean }>` +const Header = styled.div` display: flex; - padding: ${(props) => - `${props.theme.spaces[6]}px ${props.theme.spaces[8]}px`}; + padding: ${(props) => props.theme.spaces[6]}px; width: 100%; justify-content: space-between; - cursor: ${(props) => (!props.isOpen ? "pointer" : "auto")}; + align-items: center; + border-bottom: 1px solid + ${(props) => props.theme.colors.comments.appCommentsHeaderBorder}; `; -type Props = { - isOpen: boolean; - setIsOpen: (isOpen: boolean) => void; - onClose: () => void; - theme: Theme; -}; - -const AppCommentsHeader = withTheme( - ({ onClose, isOpen, setIsOpen, theme }: Props) => { - const showCommentThreads = useCallback(() => { - if (!isOpen) setIsOpen(true); - }, [isOpen]); - - return ( -
- {createMessage(COMMENTS)} - {isOpen && ( - - )} -
- ); - }, -); +function AppCommentsHeader() { + return ( +
+ {createMessage(COMMENTS)} + +
+ ); +} export default AppCommentsHeader; diff --git a/app/client/src/comments/AppComments/AppCommentsPlaceholder.tsx b/app/client/src/comments/AppComments/AppCommentsPlaceholder.tsx new file mode 100644 index 0000000000..589dec8247 --- /dev/null +++ b/app/client/src/comments/AppComments/AppCommentsPlaceholder.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import styled from "styled-components"; +import { + NO_COMMENTS_CLICK_ON_CANVAS_TO_ADD, + createMessage, +} from "constants/messages"; +import Icon, { IconSize } from "components/ads/Icon"; + +const Container = styled.div` + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + white-space: pre-line; + & .message { + text-align: center; + } + color: ${(props) => props.theme.colors.comments.appCommentsPlaceholderText}; +`; + +function AppCommentsPlaceholder() { + return ( + + + + {createMessage(NO_COMMENTS_CLICK_ON_CANVAS_TO_ADD)} + + + ); +} + +export default AppCommentsPlaceholder; diff --git a/app/client/src/comments/AppComments/Container.tsx b/app/client/src/comments/AppComments/Container.tsx index 22b0b75bae..396f221d86 100644 --- a/app/client/src/comments/AppComments/Container.tsx +++ b/app/client/src/comments/AppComments/Container.tsx @@ -1,13 +1,24 @@ import styled from "styled-components"; import { Colors } from "constants/Colors"; +import { Layers } from "constants/Layers"; -const Container = styled.div` +const Container = styled.div<{ isInline?: boolean }>` background: ${Colors.WHITE}; - width: 280px; - position: fixed; - right: 30px; - bottom: 0; - z-index: 11; + width: 250px; + ${(props) => + !props.isInline + ? ` + position: fixed; + left: 0; + top: ${props.theme.smallHeaderHeight}; + height: calc(100% - ${props.theme.smallHeaderHeight}); + ` + : ` + position: relative; + `} + z-index: ${Layers.appComments}; + display: flex; + flex-direction: column; `; export default Container; diff --git a/app/client/src/comments/CommentCard/CommentCard.tsx b/app/client/src/comments/CommentCard/CommentCard.tsx index ed09cb9ccd..876330e3b6 100644 --- a/app/client/src/comments/CommentCard/CommentCard.tsx +++ b/app/client/src/comments/CommentCard/CommentCard.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import Editor from "@draft-js-plugins/editor"; import { CompositeDecorator, @@ -8,54 +8,71 @@ import { RawDraftContentState, } from "draft-js"; import styled from "styled-components"; -import ProfileImage from "pages/common/ProfileImage"; -import { Comment } from "entities/Comments/CommentsInterfaces"; +import ProfileImage, { Profile } from "pages/common/ProfileImage"; +import { Comment, Reaction } from "entities/Comments/CommentsInterfaces"; import { getTypographyByKey } from "constants/DefaultTheme"; import CommentContextMenu from "./CommentContextMenu"; import ResolveCommentButton from "comments/CommentCard/ResolveCommentButton"; +import { MentionComponent } from "components/ads/MentionsInput"; +import Icon, { IconSize } from "components/ads/Icon"; +import EmojiReactions, { + Reaction as ComponentReaction, + Reactions, + ReactionOperation, +} from "components/ads/EmojiReactions"; +import { Toaster } from "components/ads/Toast"; +import AddCommentInput from "comments/inlineComments/AddCommentInput"; import createMentionPlugin from "@draft-js-plugins/mention"; import { flattenDeep, noop } from "lodash"; import copy from "copy-to-clipboard"; +import moment from "moment"; +import history from "utils/history"; + +import { getAppMode } from "selectors/applicationSelectors"; + +import { USER_PHOTO_URL } from "constants/userConstants"; + +import { getCommentThreadURL } from "../utils"; import { deleteCommentRequest, + markThreadAsReadRequest, pinCommentThreadRequest, + editCommentRequest, + deleteCommentThreadRequest, + addCommentReaction, + removeCommentReaction, } from "actions/commentActions"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; +import { commentThreadsSelector } from "selectors/commentsSelectors"; +import { getCurrentUser } from "selectors/usersSelectors"; +import { createMessage, LINK_COPIED_SUCCESSFULLY } from "constants/messages"; +import { Variant } from "components/ads/common"; +import TourTooltipWrapper from "components/ads/tour/TourTooltipWrapper"; +import { TourType } from "entities/Tour"; +import { getCurrentApplicationId } from "selectors/editorSelectors"; const StyledContainer = styled.div` width: 100%; padding: ${(props) => - `${props.theme.spaces[5]}px ${props.theme.spaces[7]}px`}; + `${props.theme.spaces[6]}px ${props.theme.spaces[5]}px`}; border-radius: 0; + &:hover { + background-color: ${(props) => + props.theme.colors.comments.cardHoverBackground}; + } `; -const Separator = styled.div` - background-color: ${(props) => - props.theme.colors.comments.childCommentsIndent}; - height: 1px; - width: calc(100% - ${(props) => props.theme.spaces[7] * 2}px); - margin-left: ${(props) => props.theme.spaces[7]}px; -`; - -// ${(props) => getTypographyByKey(props, "p1")}; -// line-height: 24px; -// color: ${(props) => props.theme.colors.comments.commentBody}; -// margin-top: ${(props) => props.theme.spaces[3]}px; - const CommentBodyContainer = styled.div` - background-color: ${(props) => props.theme.colors.comments.commentBackground}; - border-radius: ${(props) => props.theme.spaces[3]}px; - padding: ${(props) => - `${props.theme.spaces[4]}px ${props.theme.spaces[5]}px`}; + padding-bottom: ${(props) => props.theme.spaces[4]}px; `; const CommentHeader = styled.div` display: flex; justify-content: space-between; align-items: center; - padding-bottom: ${(props) => props.theme.spaces[5]}px; + padding-bottom: ${(props) => props.theme.spaces[4]}px; `; const UserName = styled.span` @@ -64,105 +81,409 @@ const UserName = styled.span` margin-left: ${(props) => props.theme.spaces[4]}px; display: flex; align-items: center; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; /* number of lines to show */ + -webkit-box-orient: vertical; `; const HeaderSection = styled.div` display: flex; align-items: center; + + & ${Profile} { + flex-shrink: 0; + } `; -const mentionPlugin = createMentionPlugin(); +const CommentTime = styled.div` + color: ${(props) => props.theme.colors.comments.commentTime}; + ${(props) => getTypographyByKey(props, "p3")} + display: flex; + justify-content: space-between; + margin-bottom: ${(props) => props.theme.spaces[4]}px; +`; + +const CommentSubheader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: ${(props) => props.theme.spaces[4]}px; + white-space: nowrap; + + ${(props) => getTypographyByKey(props, "p3")} + + color: ${(props) => props.theme.colors.comments.pinnedByText}; + + & .thread-id { + flex-shrink: 0; + max-width: 50px; + } + + & .pin { + margin: 0 ${(props) => props.theme.spaces[3]}px; + } + + strong { + white-space: pre; + margin-left: ${(props) => props.theme.spaces[0]}px; + text-overflow: ellipsis; + overflow: hidden; + } +`; + +const CommentThreadId = styled.div` + color: ${(props) => props.theme.colors.comments.commentTime}; + ${(props) => getTypographyByKey(props, "p3")} + overflow: hidden; + text-overflow: ellipsis; +`; + +const Section = styled.div` + display: flex; + align-items: center; + overflow: hidden; + text-overflow: ellipsis; +`; + +const UnreadIndicator = styled.div` + width: 6px; + height: 6px; + border-radius: 50%; + background-color: ${(props) => + props.theme.colors.comments.unreadIndicatorCommentCard}; + margin-right: ${(props) => props.theme.spaces[2]}px; + flex-shrink: 0; +`; + +const ReactionsRow = styled.div` + display: flex; +`; + +const EmojiReactionsBtnContainer = styled.div``; + +const mentionPlugin = createMentionPlugin({ + mentionComponent: MentionComponent, +}); const plugins = [mentionPlugin]; const decorators = flattenDeep(plugins.map((plugin) => plugin.decorators)); const decorator = new CompositeDecorator( decorators.filter((_decorator, index) => index !== 1) as DraftDecorator[], ); -const useSelectCommentUsingQuery = (commentId: string) => { - useEffect(() => { - const searchParams = new URL(window.location.href).searchParams; - const commentIdInUrl = searchParams.get("commentId"); - if (commentIdInUrl && commentIdInUrl === commentId) { - const commentCard = document.getElementById(`comment-card-${commentId}`); - commentCard?.scrollIntoView(); - } - }, []); +function StopClickPropagation({ children }: { children: React.ReactNode }) { + return ( +
e.stopPropagation()} + style={{ display: "flex" }} + > + {children} +
+ ); +} + +const replyText = (replies?: number) => { + if (!replies) return ""; + return replies > 1 ? `${replies} Replies` : `1 Reply`; }; +enum CommentCardModes { + EDIT = "EDIT", + VIEW = "VIEW", +} + +const reduceReactions = ( + reactions: Array | undefined, + username?: string, +) => { + return ( + (Array.isArray(reactions) && + reactions.reduce( + (res: Record, reaction: Reaction) => { + const { byUsername, emoji } = reaction; + const sameAsCurrent = byUsername === username; + if (res[reaction.emoji]) { + res[reaction.emoji].count++; + if (!sameAsCurrent) { + res[reaction.emoji].users = [ + ...(res[reaction.emoji].users || []), + byUsername, + ]; + } + } else { + const users = !sameAsCurrent ? [byUsername] : []; + res[emoji] = { + count: 1, + reactionEmoji: emoji, + users, + } as ComponentReaction; + } + + if (sameAsCurrent) { + res[reaction.emoji].active = true; + } + + return res; + }, + {}, + )) || + undefined + ); +}; + +const ResolveButtonContainer = styled.div` + margin-left: ${(props) => props.theme.spaces[2]}px; +`; + function CommentCard({ comment, - isParentComment, - toggleResolved, - resolved, commentThreadId, + inline, + isParentComment, + numberOfReplies, + resolved, + showReplies, + showSubheader, + toggleResolved, + unread = true, + visible, }: { comment: Comment; + isEditMode?: boolean; isParentComment?: boolean; resolved?: boolean; toggleResolved?: () => void; commentThreadId: string; + numberOfReplies?: number; + showReplies?: boolean; + showSubheader?: boolean; + unread?: boolean; + inline?: boolean; + visible?: boolean; }) { + const [isHovered, setIsHovered] = useState(false); + const [cardMode, setCardMode] = useState(CommentCardModes.VIEW); const dispatch = useDispatch(); - const { authorName, body, id: commentId } = comment; + const { authorName, authorUsername, body, id: commentId } = comment; const contentState = convertFromRaw(body as RawDraftContentState); const editorState = EditorState.createWithContent(contentState, decorator); + const commentThread = useSelector(commentThreadsSelector(commentThreadId)); + const [reactions, setReactions] = useState(); + const currentUser = useSelector(getCurrentUser); + const currentUserUsername = currentUser?.username; - const copyCommentLink = useCallback(() => { - const url = new URL(window.location.href); - url.searchParams.set("commentId", commentId); - url.searchParams.set("commentThreadId", commentThreadId); - url.searchParams.set("isCommentMode", "true"); - copy(url.toString()); - }, []); + const isPinned = commentThread.pinnedState?.active; + const pinnedByUsername = commentThread.pinnedState?.authorUsername; + let pinnedBy = commentThread.pinnedState?.authorName; + + const appMode = useSelector(getAppMode); + + if (currentUserUsername === pinnedByUsername) { + pinnedBy = "You"; + } + + const applicationId = useSelector(getCurrentApplicationId); + + const commentThreadURL = getCommentThreadURL({ + applicationId, + commentThreadId, + isResolved: !!commentThread?.resolvedState?.active, + pageId: commentThread?.pageId, + mode: appMode, + }); + + const copyCommentLink = () => { + copy(commentThreadURL.toString()); + Toaster.show({ + text: createMessage(LINK_COPIED_SUCCESSFULLY), + variant: Variant.success, + }); + }; const pin = useCallback(() => { - dispatch(pinCommentThreadRequest({ threadId: commentThreadId })); - }, []); + dispatch( + pinCommentThreadRequest({ threadId: commentThreadId, pin: !isPinned }), + ); + }, [isPinned]); const deleteComment = useCallback(() => { dispatch(deleteCommentRequest({ threadId: commentThreadId, commentId })); }, []); + const deleteThread = () => { + dispatch(deleteCommentThreadRequest(commentThreadId)); + }; + + const isCreatedByMe = currentUserUsername === comment.authorUsername; + + const switchToEditCommentMode = () => setCardMode(CommentCardModes.EDIT); + const switchToViewCommentMode = () => setCardMode(CommentCardModes.VIEW); + + const onSaveComment = (body: RawDraftContentState) => { + dispatch(editCommentRequest({ commentId, commentThreadId, body })); + setCardMode(CommentCardModes.VIEW); + }; + const contextMenuProps = { + switchToEditCommentMode, pin, copyCommentLink, deleteComment, + deleteThread, + isParentComment, + isCreatedByMe, + isPinned, }; - useSelectCommentUsingQuery(comment.id); + // TODO enable when comments links are enabled + // useSelectCommentUsingQuery(comment.id); + + // Dont make inline cards clickable + const handleCardClick = () => { + if (inline) return; + history.push( + `${commentThreadURL.pathname}${commentThreadURL.search}${commentThreadURL.hash}`, + ); + if (!commentThread.isViewed) { + dispatch(markThreadAsReadRequest(commentThreadId)); + } + }; + + useEffect(() => { + setReactions(reduceReactions(comment.reactions, currentUserUsername)); + }, [comment.reactions]); + + const handleReaction = ( + _event: React.MouseEvent, + emojiData: string, + updatedReactions: Reactions, + addOrRemove: ReactionOperation, + ) => { + setReactions(updatedReactions); + if (addOrRemove == ReactionOperation.ADD) { + dispatch(addCommentReaction({ emoji: emojiData, commentId })); + } else { + dispatch(removeCommentReaction({ emoji: emojiData, commentId })); + } + }; + + const showOptions = visible || isHovered; + + const showResolveBtn = + (showOptions || !!resolved) && isParentComment && toggleResolved; + + const hasReactions = !!reactions && Object.keys(reactions).length > 0; return ( - <> - - - - - {authorName} - - - {isParentComment && toggleResolved && ( - + setIsHovered(false)} + onMouseOver={() => setIsHovered(true)} + > + {showSubheader && ( + +
+ {unread && } + {commentThread.sequenceId} +
+
+ {isPinned && ( + <> + + Pinned By + {` ${pinnedBy}`} + )} - - - - +
+
+ )} + + + + {authorName} + + + {showOptions && ( + + + + + + )} + {showResolveBtn && ( + + + {inline ? ( + + void} + resolved={!!resolved} + /> + + ) : ( + void} + resolved={!!resolved} + /> + )} + + + )} + {showOptions && ( + + + + )} + + + + {cardMode === CommentCardModes.EDIT ? ( + + ) : ( - -
- {!isParentComment && } - + )} + + + {moment(comment.creationTime).fromNow()} + {showReplies && replyText(numberOfReplies)} + + {hasReactions && ( + + + + + + )} +
); } diff --git a/app/client/src/comments/CommentCard/CommentContextMenu.tsx b/app/client/src/comments/CommentCard/CommentContextMenu.tsx index bef783b01c..17cd05dc5d 100644 --- a/app/client/src/comments/CommentCard/CommentContextMenu.tsx +++ b/app/client/src/comments/CommentCard/CommentContextMenu.tsx @@ -5,7 +5,10 @@ import { PIN_COMMENT, COPY_LINK, DELETE_COMMENT, + DELETE_THREAD, + UNPIN_COMMENT, createMessage, + EDIT_COMMENT, } from "constants/messages"; import { noop } from "lodash"; @@ -14,9 +17,7 @@ import "@blueprintjs/popover2/lib/css/blueprint-popover2.css"; import { Popover2 } from "@blueprintjs/popover2"; // render over popover portals -const Container = styled.div` - z-index: 11; -`; +const Container = styled.div``; const MenuItem = styled.div` display: flex; @@ -29,14 +30,7 @@ const MenuItem = styled.div` `; const StyledIcon = styled(Icon)` - && path { - stroke: ${(props) => props.theme.colors.comments.contextMenuIcon}; - fill: ${(props) => props.theme.colors.comments.contextMenuIcon}; - } - ${MenuItem}:hover & path { - stroke: ${(props) => - props.theme.colors.comments.contextMenuIconStrokeHover}; - } + margin-left: ${(props) => props.theme.spaces[2]}px; `; const MenuIcon = styled.div` @@ -56,31 +50,68 @@ type Props = { pin: typeof noop; copyCommentLink: typeof noop; deleteComment: typeof noop; + deleteThread: typeof noop; + switchToEditCommentMode: typeof noop; + isParentComment?: boolean; + isCreatedByMe?: boolean; + isPinned?: boolean; }; -function CommentContextMenu({ pin, copyCommentLink, deleteComment }: Props) { +function CommentContextMenu({ + copyCommentLink, + deleteComment, + deleteThread, + isCreatedByMe, + isParentComment, + isPinned, + pin, + switchToEditCommentMode, +}: Props) { const [isOpen, setIsOpen] = useState(false); - const options = useMemo( - () => [ - { - icon: "pin-2", - display: createMessage(PIN_COMMENT), - onClick: pin, - }, - { - icon: "link-2", - display: createMessage(COPY_LINK), - onClick: copyCommentLink, - }, - { - icon: "trash", - display: createMessage(DELETE_COMMENT), - onClick: deleteComment, - }, - ], - [], - ); + const options = useMemo(() => { + const options = []; + if (isParentComment) { + options.push( + { + icon: isPinned ? "unpin" : "pin-3", + display: isPinned + ? createMessage(UNPIN_COMMENT) + : createMessage(PIN_COMMENT), + onClick: pin, + }, + { + icon: "link-2", + display: createMessage(COPY_LINK), + onClick: copyCommentLink, + }, + ); + } + + if (isCreatedByMe) { + if (!isParentComment) { + options.push({ + icon: "trash", + display: createMessage(DELETE_COMMENT), + onClick: deleteComment, + }); + } else { + options.push({ + icon: "trash", + display: createMessage(DELETE_THREAD), + onClick: deleteThread, + }); + } + + options.push({ + icon: "edit", + display: createMessage(EDIT_COMMENT), + onClick: switchToEditCommentMode, + }); + } + + return options; + }, [isPinned]); const handleInteraction = useCallback((isOpen) => { setIsOpen(isOpen); @@ -91,6 +122,8 @@ function CommentContextMenu({ pin, copyCommentLink, deleteComment }: Props) { option.onClick(); }, []); + if (!options.length) return null; + return ( ( handleClick(option)}> - + {option.display} @@ -107,11 +144,12 @@ function CommentContextMenu({ pin, copyCommentLink, deleteComment }: Props) { } isOpen={isOpen} minimal + modifiers={{ offset: { enabled: true, options: { offset: [7, 15] } } }} onInteraction={handleInteraction} placement={"bottom-end"} portalClassName="comment-context-menu" > - + ); } diff --git a/app/client/src/comments/CommentCard/ResolveCommentButton.tsx b/app/client/src/comments/CommentCard/ResolveCommentButton.tsx index a5e2e5081c..2c3062ba0e 100644 --- a/app/client/src/comments/CommentCard/ResolveCommentButton.tsx +++ b/app/client/src/comments/CommentCard/ResolveCommentButton.tsx @@ -6,7 +6,6 @@ import { Theme } from "constants/DefaultTheme"; const Container = styled.div` display: flex; cursor: pointer; - margin-right: ${(props) => props.theme.spaces[4]}px; `; type Props = { @@ -15,30 +14,51 @@ type Props = { theme: Theme; }; -const StyledResolveIcon = styled(Icon)<{ strokeColor: string }>` - & circle, - & path { - stroke: ${(props) => props.strokeColor}; +const StyledResolveIcon = styled(Icon)<{ + strokeColorCircle: string; + strokeColorPath: string; + fillColor: string; +}>` + & circle { + stroke: ${(props) => props.strokeColorCircle}; } && path { + stroke: ${(props) => props.strokeColorPath}; fill: transparent; } + && svg { + fill: ${(props) => props.fillColor}; + } `; const ResolveCommentButton = withTheme( ({ handleClick, resolved, theme }: Props) => { const { resolved: resolvedColor, + resolvedFill: resolvedFillColor, + resolvedPath: resolvedPathColor, unresolved: unresolvedColor, + unresolvedFill: unresolvedFillColor, } = theme.colors.comments; - const strokeColor = resolved ? resolvedColor : unresolvedColor; + + const strokeColorCircle = resolved ? resolvedColor : unresolvedColor; + const strokeColorPath = resolved ? resolvedPathColor : unresolvedColor; + const fillColor = resolved ? resolvedFillColor : unresolvedFillColor; + + const _handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + handleClick(); + }; + return ( - + ); diff --git a/app/client/src/comments/CommentThread/CommentThread.tsx b/app/client/src/comments/CommentThread/CommentThread.tsx index c6f4fb9b86..9071fcb4df 100644 --- a/app/client/src/comments/CommentThread/CommentThread.tsx +++ b/app/client/src/comments/CommentThread/CommentThread.tsx @@ -1,5 +1,5 @@ import React, { useRef } from "react"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import CommentCard from "comments/CommentCard/CommentCard"; import AddCommentInput from "comments/inlineComments/AddCommentInput"; @@ -7,38 +7,46 @@ import ScrollToLatest from "./ScrollToLatest"; import { addCommentToThreadRequest, + markThreadAsReadRequest, + resetVisibleThread, setCommentResolutionRequest, } from "actions/commentActions"; +import { shouldShowResolved as shouldShowResolvedSelector } from "selectors/commentsSelectors"; + import useIsScrolledToBottom from "utils/hooks/useIsScrolledToBottom"; import { CommentThread } from "entities/Comments/CommentsInterfaces"; import { RawDraftContentState } from "draft-js"; import styled from "styled-components"; +import { animated } from "react-spring"; +import { AppState } from "reducers"; +import { useEffect } from "react"; -const ThreadContainer = styled.div` - width: 400px; +const ThreadContainer = styled(animated.div)<{ + visible?: boolean; + inline?: boolean; + pinned?: boolean; +}>` + width: 280px; max-width: 100%; + background-color: ${(props) => + props.inline + ? "transparent" + : props.pinned + ? props.theme.colors.comments.pinnedThreadBackground + : props.visible + ? props.theme.colors.comments.visibleThreadBackground + : "transparent"}; + max-height: ${(props) => + props.inline ? `calc(100vh - ${props.theme.smallHeaderHeight})` : "unset"}; + /* overflow: auto collapses the comment threads in the sidebar */ + overflow: ${(props) => (props.inline ? "auto" : "unset")}; `; const CommentsContainer = styled.div<{ inline?: boolean }>` position: relative; - max-height: ${(props) => (!props.inline ? "unset" : "285px")}; - overflow: auto; -`; - -const ChildCommentsContainer = styled.div` - display: flex; -`; - -const ChildCommentIndent = styled.div` - width: 1px; - background-color: ${(props) => - props.theme.colors.comments.childCommentsIndent}; - margin-left: ${(props) => props.theme.spaces[11]}px; - margin-bottom: ${(props) => props.theme.spaces[7]}px; - margin-top: ${(props) => props.theme.spaces[5]}px; `; const ChildComments = styled.div` @@ -47,19 +55,38 @@ const ChildComments = styled.div` function CommentThreadContainer({ commentThread, + hideChildren, hideInput, inline, + showSubheader, }: { commentThread: CommentThread; isOpen?: boolean; hideInput?: boolean; inline?: boolean; + hideChildren?: boolean; + showSubheader?: boolean; }) { const dispatch = useDispatch(); - const { comments, id: commentThreadId } = commentThread; + const { comments, id: commentThreadId } = commentThread || {}; const messagesBottomRef = useRef(null); const commentsContainerRef = useRef(null); + const shouldShowResolved = useSelector(shouldShowResolvedSelector); + const isThreadVisible = + shouldShowResolved || !commentThread?.resolvedState?.active; + + const isVisible = useSelector( + (state: AppState) => + state.ui.comments.visibleCommentThreadId === commentThreadId, + ); + + useEffect(() => { + if (isVisible && !commentThread?.isViewed) { + dispatch(markThreadAsReadRequest(commentThreadId)); + } + }, [isVisible, commentThread?.isViewed]); + // Check if the comments window is scrolled to the bottom // We don't autoscroll for the user receiving the updates // for better UX, instead we'd show a helper message to indicate @@ -90,42 +117,58 @@ function CommentThreadContainer({ const resolveCommentThread = () => { dispatch( setCommentResolutionRequest({ - threadId: commentThread.id, - resolved: !commentThread.resolved, + threadId: commentThread?.id, + resolved: !commentThread?.resolvedState?.active, }), ); }; const parentComment = Array.isArray(comments) && comments[0]; const childComments = Array.isArray(comments) && comments.slice(1); + const numberOfReplies = + (Array.isArray(childComments) && childComments.length) || 0; - return ( - + const handleCancel = () => dispatch(resetVisibleThread(commentThreadId)); + + if (!commentThread) return null; + + return isThreadVisible ? ( +
{parentComment && ( )} - {childComments && childComments.length > 0 && ( - - - - {childComments.map((comment) => ( - - ))} - - + {!hideChildren && childComments && childComments.length > 0 && ( + + {childComments.map((comment) => ( + + ))} + )}
@@ -133,9 +176,11 @@ function CommentThreadContainer({ )}
- {!hideInput && } + {!hideInput && ( + + )} - ); + ) : null; } export default CommentThreadContainer; diff --git a/app/client/src/comments/CommentThread/ScrollToLatest.tsx b/app/client/src/comments/CommentThread/ScrollToLatest.tsx index 0f442099a6..8727e18b05 100644 --- a/app/client/src/comments/CommentThread/ScrollToLatest.tsx +++ b/app/client/src/comments/CommentThread/ScrollToLatest.tsx @@ -1,15 +1,37 @@ +import Icon from "components/ads/Icon"; +import { Theme } from "constants/DefaultTheme"; import React from "react"; -import styled from "styled-components"; +import styled, { withTheme } from "styled-components"; +import { getTypographyByKey } from "constants/DefaultTheme"; +import { VIEW_LATEST, createMessage } from "constants/messages"; const Container = styled.div` position: absolute; right: 10px; bottom: 10px; cursor: pointer; + color: ${(props) => props.theme.colors.comments.viewLatest}; + text-transform: uppercase; + display: flex; + ${(props) => getTypographyByKey(props, "btnMedium")} + & .view-latest { + margin-right: ${(props) => props.theme.spaces[1]}px; + } `; -function ScrollToLatest({ scrollToBottom }: { scrollToBottom: () => void }) { - return View latest; -} +const ScrollToLatest = withTheme( + ({ scrollToBottom, theme }: { scrollToBottom: () => void; theme: Theme }) => { + return ( + + {createMessage(VIEW_LATEST)} + + + ); + }, +); export default ScrollToLatest; diff --git a/app/client/src/comments/CommentsShowcaseCarousel/CommentsCarouselModal.tsx b/app/client/src/comments/CommentsShowcaseCarousel/CommentsCarouselModal.tsx new file mode 100644 index 0000000000..066d2418fc --- /dev/null +++ b/app/client/src/comments/CommentsShowcaseCarousel/CommentsCarouselModal.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import ModalComponent from "components/designSystems/blueprint/ModalComponent"; +import { Layers } from "constants/Layers"; + +function ShowcaseCarouselModal({ children }: { children: React.ReactNode }) { + return ( + { + // TODO (rishabh) handle close + }} + overlayClassName="comments-onboarding-carousel" + portalClassName="comments-onboarding-carousel-portal" + scrollContents + width={325} + zIndex={Layers.appComments} + > + {children} + + ); +} + +export default ShowcaseCarouselModal; diff --git a/app/client/src/comments/CommentsShowcaseCarousel/ProfileForm.tsx b/app/client/src/comments/CommentsShowcaseCarousel/ProfileForm.tsx new file mode 100644 index 0000000000..03f749f8b1 --- /dev/null +++ b/app/client/src/comments/CommentsShowcaseCarousel/ProfileForm.tsx @@ -0,0 +1,81 @@ +import React from "react"; + +import { reduxForm } from "redux-form"; + +import FormGroup from "components/ads/formFields/FormGroup"; +import FormTextField from "components/ads/formFields/TextField"; + +import UserProfileImagePicker from "components/ads/UserProfileImagePicker"; + +import { createMessage, DISPLAY_NAME, EMAIL_ADDRESS } from "constants/messages"; +import styled from "styled-components"; + +import { isEmail, isEmptyString } from "utils/formhelpers"; + +export type FormValues = { + fullName?: string; + displayName?: string; + emailAddress?: string; +}; + +const Container = styled.div` + padding: ${(props) => props.theme.spaces[5]}px; +`; + +export const PROFILE_FORM = "PROFILE_FORM"; + +const fieldNames = { + displayName: "displayName", + emailAddress: "emailAddress", +}; + +const validate = (values: any) => { + const errors: any = {}; + const displayName = values[fieldNames.displayName] || ""; + const emailAddress = values[fieldNames.emailAddress] || ""; + + if (!displayName || isEmptyString(displayName)) { + errors[fieldNames.displayName] = "Required"; + } + if (!emailAddress || isEmptyString(emailAddress) || !isEmail(emailAddress)) { + errors[fieldNames.emailAddress] = "Required"; + } + + return errors; +}; + +function ProfileForm(props: any) { + return ( + +
+ +
+ + + + + + +
+ ); +} + +export default reduxForm({ + // Currently while using this feature, + // a destroy action is dispatched + // so the initial values don't get set + // TODO: triage this issue + destroyOnUnmount: false, + form: PROFILE_FORM, + validate, +})(ProfileForm); diff --git a/app/client/src/comments/CommentsShowcaseCarousel/index.tsx b/app/client/src/comments/CommentsShowcaseCarousel/index.tsx new file mode 100644 index 0000000000..76d725e080 --- /dev/null +++ b/app/client/src/comments/CommentsShowcaseCarousel/index.tsx @@ -0,0 +1,176 @@ +import React from "react"; + +import Text, { TextType } from "components/ads/Text"; +import ShowcaseCarousel, { Steps } from "components/ads/ShowcaseCarousel"; +import ProfileForm, { PROFILE_FORM } from "./ProfileForm"; +import CommentsCarouselModal from "./CommentsCarouselModal"; + +import styled, { withTheme } from "styled-components"; +import { Theme } from "constants/DefaultTheme"; +import { useDispatch, useSelector } from "react-redux"; +import { getFormSyncErrors } from "redux-form"; +import { getFormValues } from "redux-form"; + +import { isIntroCarouselVisibleSelector } from "selectors/commentsSelectors"; +import { getCurrentUser } from "selectors/usersSelectors"; + +import { setActiveTour } from "actions/tourActions"; +import { TourType } from "entities/Tour"; +import { hideCommentsIntroCarousel } from "actions/commentActions"; +import { setCommentsIntroSeen } from "utils/storage"; + +import { updateUserDetails } from "actions/userActions"; + +import { S3_BUCKET_URL } from "constants/ThirdPartyConstants"; + +const getBanner = (step: number) => + `${S3_BUCKET_URL}/comments/step-${step}.png`; + +const introSteps = [ + { + title: "Introducing Live Comments", + content: + "We are introducing live comments. From now on you will be able to comment on your apps, tag other people and exchange thoughts in threads. Click β€˜Next’ to learn more about comments and start commenting.", + banner: getBanner(1), + hideBackBtn: true, + }, + { + title: "Give feedback", + content: + "Comment on your co-worker’s work and share your thoughts on what works and what needs change.", + banner: getBanner(2), + }, + { + title: "Invite other people to your conversations", + content: + "When leaving a comment you can tag other people by writing β€˜@’ and their name. This way the person you tagged will get a notification and an e-mail that you tagged them in a comment.", + banner: getBanner(3), + }, + { + title: "Tag a comment to a widget", + content: + "If you click on a component while in a comment mode you will tag that comment to that widget. This way if the widget is moved the comment will be moved as well. You can disconnect the comment and widget y simply moving the the comment away from the widget.", + banner: getBanner(4), + }, + { + title: "You are all set!", + content: + "By clicking on the comments icon in the top right corner you will activate the β€˜collaboration mode’ and will be able to start a thread or answer to someone else’s comment.", + banner: getBanner(5), + }, +]; + +const IntroContentContainer = styled.div` + padding: ${(props) => props.theme.spaces[5]}px; +`; + +const StyledImg = styled.img` + width: 100%; + object-fit: contain; +`; + +function IntroStep(props: { + title: string; + content: string; + banner: string; + theme: Theme; +}) { + return ( + <> + + +
+ + {props.title} + +
+ + {props.content} + +
+ + ); +} + +const IntroStepThemed = withTheme(IntroStep); + +const getSteps = ( + onSubmitProfileForm: any, + isSubmitProfileFormDisabled: boolean, + startTutorial: () => void, + initialProfileFormValues: { emailAddress?: string; displayName?: string }, + emailDisabled: boolean, +) => [ + ...introSteps.slice(0, 4).map((stepConfig: any) => ({ + props: stepConfig, + component: IntroStepThemed, + })), + { + component: ProfileForm, + props: { + isSubmitDisabled: isSubmitProfileFormDisabled, + onSubmit: onSubmitProfileForm, + initialValues: initialProfileFormValues, + emailDisabled, + }, + }, + { + component: IntroStepThemed, + props: { + ...introSteps[4], + hideBackBtn: true, + nextBtnText: "Start Tutorial", + onSubmit: startTutorial, + }, + }, +]; + +export default function CommentsShowcaseCarousel() { + const dispatch = useDispatch(); + const isIntroCarouselVisible = useSelector(isIntroCarouselVisibleSelector); + const profileFormValues = useSelector(getFormValues(PROFILE_FORM)); + const profileFormErrors = useSelector(getFormSyncErrors("PROFILE_FORM")); + const isSubmitDisabled = Object.keys(profileFormErrors).length !== 0; + + const currentUser = useSelector(getCurrentUser); + const { email, name } = currentUser || {}; + + const initialProfileFormValues = { emailAddress: email, displayName: name }; + const onSubmitProfileForm = () => { + const { displayName: name, emailAddress: email } = profileFormValues as { + displayName: string; + emailAddress: string; + }; + dispatch(updateUserDetails({ name, email })); + }; + + const startTutorial = () => { + dispatch(setActiveTour(TourType.COMMENTS_TOUR)); + dispatch(hideCommentsIntroCarousel()); + setCommentsIntroSeen(true); + }; + + const steps = getSteps( + onSubmitProfileForm, + isSubmitDisabled, + startTutorial, + initialProfileFormValues, + !!email, + ); + + if (!isIntroCarouselVisible) return null; + + return ( + + + + ); +} diff --git a/app/client/src/comments/ToggleCommentModeButton.tsx b/app/client/src/comments/ToggleCommentModeButton.tsx deleted file mode 100644 index 0c69ec17b4..0000000000 --- a/app/client/src/comments/ToggleCommentModeButton.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React, { useCallback, useEffect } from "react"; -import styled from "styled-components"; -import { useDispatch, useSelector } from "react-redux"; -import { ReactComponent as ToggleCommmentMode } from "assets/icons/comments/toggle-comment-mode.svg"; -import { - setCommentMode as setCommentModeAction, - fetchApplicationCommentsRequest, -} from "actions/commentActions"; -import { - commentModeSelector, - areCommentsEnabledForUser as areCommentsEnabledForUserSelector, -} from "../selectors/commentsSelectors"; -import { useLocation } from "react-router"; -import history from "utils/history"; - -const StyledToggleCommentMode = styled.div<{ isCommentMode: boolean }>` - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - - background: ${(props) => - !props.isCommentMode - ? props.theme.colors.comments.commentModeButtonBackground - : props.theme.colors.comments.commentModeButtonIcon}; - svg path { - fill: ${(props) => - props.isCommentMode - ? "#fff" - : props.theme.colors.comments.commentModeButtonIcon}; - } - - height: ${(props) => props.theme.smallHeaderHeight}; - width: ${(props) => props.theme.smallHeaderHeight}; -`; - -// update isCommentMode in the store based on the query search param -const useUpdateCommentModeInStore = () => { - const location = useLocation(); - const dispatch = useDispatch(); - - const setCommentModeInStore = useCallback( - (updatedIsCommentMode) => - dispatch(setCommentModeAction(updatedIsCommentMode)), - [], - ); - - useEffect(() => { - if (window.location.href) { - const searchParams = new URL(window.location.href).searchParams; - const isCommentMode = searchParams.get("isCommentMode"); - if (isCommentMode) { - const updatedIsCommentMode = isCommentMode === "true" ? true : false; - setCommentModeInStore(updatedIsCommentMode); - } - } - }, [location]); -}; - -/** - * Toggle comment mode: - * This component is also responsible for fetching - * application comments - */ -function ToggleCommentModeButton() { - const dispatch = useDispatch(); - - const commentsEnabled = useSelector(areCommentsEnabledForUserSelector); - const isCommentMode = useSelector(commentModeSelector); - - const setCommentModeInUrl = useCallback(() => { - const currentURL = new URL(window.location.href); - const searchParams = currentURL.searchParams; - searchParams.set("isCommentMode", `${!isCommentMode}`); - // remove comment link params so that they don't get retriggered - // on toggling comment mode - searchParams.delete("commentId"); - searchParams.delete("commentThreadId"); - history.replace({ - pathname: currentURL.pathname, - search: searchParams.toString(), - hash: currentURL.hash, - }); - }, [isCommentMode]); - - // fetch applications comments when comment mode is turned on - useEffect(() => { - if (isCommentMode) { - dispatch(fetchApplicationCommentsRequest()); - } - }, [isCommentMode]); - - useUpdateCommentModeInStore(); - - return commentsEnabled ? ( - - - - ) : null; -} - -export default ToggleCommentModeButton; diff --git a/app/client/src/comments/init.ts b/app/client/src/comments/init.ts deleted file mode 100644 index f3db7d4a4b..0000000000 --- a/app/client/src/comments/init.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { updateAndSaveLayout } from "actions/pageActions"; -import { uniqueId } from "lodash"; - -const dsl = require("./dsl.json"); - -export const updateLayout = () => updateAndSaveLayout(dsl.widgets as any); - -export const getTestComments = () => { - const commentThreads = Object.entries(dsl.widgets).map(([widgetId]) => { - return { - refId: widgetId, - position: { top: 10, left: 15 }, - id: uniqueId(), - comments: [{ body: widgetId, authorName: uniqueId() }], - }; - }); - - // const [widgetId] = Object.entries(dsl.widgets)[0]; - // const commentThreads = [ - // { - // refId: widgetId, - // meta: { - // position: { top: 10, left: 15 }, - // }, - // id: `${1}`, - // comments: [{ body: widgetId }], - // }, - // ]; - - return commentThreads; -}; diff --git a/app/client/src/comments/inlineComments/AddCommentInput.tsx b/app/client/src/comments/inlineComments/AddCommentInput.tsx index 5d140e9147..e8b7d44aff 100644 --- a/app/client/src/comments/inlineComments/AddCommentInput.tsx +++ b/app/client/src/comments/inlineComments/AddCommentInput.tsx @@ -6,35 +6,38 @@ import React, { useMemo, useState, } from "react"; -import Icon, { IconSize } from "components/ads/Icon"; -import styled, { withTheme } from "styled-components"; -import { EditorState, convertToRaw, Modifier, SelectionState } from "draft-js"; import EmojiPicker from "components/ads/EmojiPicker"; -import MentionsInput from "components/ads/MentionsInput"; -import useOrgUsers from "./useOrgUsers"; -import { MentionData } from "@draft-js-plugins/mention"; -import { OrgUser } from "constants/orgConstants"; -import { IEmojiData } from "emoji-picker-react"; +import MentionsInput, { Trigger } from "components/ads/MentionsInput"; +import Button, { Category } from "components/ads/Button"; -import { createMessage, ADD_COMMENT_PLACEHOLDER } from "constants/messages"; +import { BaseEmoji } from "emoji-mart"; +import styled from "styled-components"; +import { EditorState, convertToRaw, Modifier, SelectionState } from "draft-js"; +import { MentionData } from "@draft-js-plugins/mention"; + +import { OrgUser } from "constants/orgConstants"; +import useOrgUsers from "./useOrgUsers"; + +import { RawDraftContentState } from "draft-js"; + +import { + createMessage, + ADD_COMMENT_PLACEHOLDER, + CANCEL, + POST, +} from "constants/messages"; + +import { setShowAppInviteUsersDialog } from "actions/applicationActions"; +import { useDispatch } from "react-redux"; +import { change } from "redux-form"; + +import { INVITE_USERS_TO_ORG_FORM } from "constants/forms"; + +import { isEmail } from "utils/formhelpers"; const StyledInputContainer = styled.div` - display: flex; - align-items: center; width: 100%; - padding: ${(props) => - `${props.theme.spaces[3]}px ${props.theme.spaces[4]}px`}; - background: ${(props) => - props.theme.colors.comments.addCommentInputBackground}; -`; - -const StyledSendButton = styled.button` - display: inline-flex; - background: transparent; - border: none; - align-items: center; - position: relative; - top: -1px; + margin-bottom: ${(props) => props.theme.spaces[7]}px; `; const StyledEmojiTrigger = styled.div` @@ -43,11 +46,24 @@ const StyledEmojiTrigger = styled.div` margin-right: ${(props) => props.theme.spaces[4]}px; `; -const PaddingContainer = styled.div` +const PaddingContainer = styled.div<{ removePadding?: boolean }>` padding: ${(props) => - `${props.theme.spaces[4]}px ${props.theme.spaces[6]}px`}; + !props.removePadding + ? `${props.theme.spaces[7]}px ${props.theme.spaces[5]}px` + : 0}; + + & .cancel-button { + margin-right: ${(props) => props.theme.spaces[5]}px; + } `; +const Row = styled.div` + display: flex; + justify-content: space-between; +`; + +// Trigger tests + const insertCharacter = ( characterToInsert: string, editorState: EditorState, @@ -95,88 +111,150 @@ const resetEditorState = (editorState: EditorState) => { return editorState; }; +const otherSuggestions = [{ name: "appsmith" }]; + const useUserSuggestions = ( users: Array, setSuggestions: Dispatch>>, ) => { useEffect(() => { - setSuggestions(users.map((user) => ({ name: user.username }))); + setSuggestions([ + ...otherSuggestions, + ...users.map((user) => ({ name: user.name || user.username, user })), + ]); }, [users]); }; -const AddCommentInput = withTheme(({ onSave, theme }: any) => { +function AddCommentInput({ + initialEditorState, + onCancel, + onSave, + removePadding, +}: { + removePadding?: boolean; + initialEditorState?: EditorState; + onSave: (state: RawDraftContentState) => void; + onCancel?: () => void; +}) { + const dispatch = useDispatch(); const users = useOrgUsers(); const [suggestions, setSuggestions] = useState>([]); + const [trigger, setTrigger] = useState(); useUserSuggestions(users, setSuggestions); - const [editorState, setEditorState] = useState(EditorState.createEmpty()); + const [editorState, setEditorState] = useState( + initialEditorState || EditorState.createEmpty(), + ); const [suggestionsQuery, setSuggestionsQuery] = useState(""); + const clearEditor = useCallback(() => { + setEditorState(resetEditorState(editorState)); + }, [editorState]); + + const _onCancel = () => { + clearEditor(); + if (onCancel) onCancel(); + }; + const onSaveComment = useCallback( (editorStateArg?: EditorState) => { const latestEditorState = editorStateArg || editorState; + const plainText = latestEditorState.getCurrentContent().getPlainText(); - if (!latestEditorState.getCurrentContent().hasText()) return; + if (!plainText || plainText.trim().length === 0) return; const contentState = latestEditorState.getCurrentContent(); const rawContent = convertToRaw(contentState); + clearEditor(); onSave(rawContent); - setEditorState(resetEditorState(latestEditorState)); }, [editorState], ); const handleSubmit = useCallback(() => onSaveComment(), [editorState]); const handleEmojiClick = useCallback( - (e: React.MouseEvent, emojiObject: IEmojiData) => { - const newEditorState = insertCharacter(emojiObject.emoji, editorState); + (e: React.MouseEvent, emojiObject: BaseEmoji) => { + const newEditorState = insertCharacter(emojiObject.native, editorState); setEditorState(newEditorState); }, [editorState], ); const onSearchChange = useCallback( - ({ value }: { value: string }) => { + ({ trigger, value }: { trigger: string; value: string }) => { setSuggestionsQuery(value); + setTrigger(trigger as Trigger); }, [suggestions], ); const filteredSuggestions = useMemo(() => { - if (!suggestionsQuery) return suggestions; + let suggestionResults = suggestions; + if (!suggestionsQuery) return suggestionResults; else { - return suggestions.filter((suggestion) => { - const str = suggestion.name.toLowerCase(); + suggestionResults = suggestions.filter((suggestion) => { + const name = suggestion.name.toLowerCase(); + const username = suggestion.user?.username.toLowerCase() || ""; const filter = suggestionsQuery.toLowerCase(); - return str.indexOf(filter) !== -1; + return name.indexOf(filter) !== -1 || username.indexOf(filter) !== -1; }); } - }, [suggestionsQuery, suggestions]); + + if (suggestionResults.length !== 0) return suggestionResults; + + if (isEmail(suggestionsQuery)) { + return [{ name: suggestionsQuery, isInviteTrigger: true }]; + } + + return []; + }, [suggestionsQuery, suggestions, trigger]); + + const onAddMention = (mention: MentionData) => { + if (isEmail(mention.name) && mention.isInviteTrigger) { + dispatch(setShowAppInviteUsersDialog(true)); + dispatch(change(INVITE_USERS_TO_ORG_FORM, "users", mention.name)); + } + }; return ( - - - + + + + + + + - - +
+ ); +} export default UnpublishedCommentThread; diff --git a/app/client/src/comments/inlineComments/useOrgUsers.tsx b/app/client/src/comments/inlineComments/useOrgUsers.tsx index 642a49c28b..bcf055d5f1 100644 --- a/app/client/src/comments/inlineComments/useOrgUsers.tsx +++ b/app/client/src/comments/inlineComments/useOrgUsers.tsx @@ -8,12 +8,14 @@ const useOrgUsers = () => { const orgId = useSelector(getCurrentOrgId); const orgUsers = useSelector(getAllUsers); useEffect(() => { - dispatch({ - type: ReduxActionTypes.FETCH_ALL_USERS_INIT, - payload: { - orgId, - }, - }); + if (!orgUsers || !orgUsers.length) { + dispatch({ + type: ReduxActionTypes.FETCH_ALL_USERS_INIT, + payload: { + orgId, + }, + }); + } }, [orgId]); return orgUsers; }; diff --git a/app/client/src/comments/tests/Comments.test.tsx b/app/client/src/comments/tests/Comments.test.tsx index 07ad0806b6..dd3cb0fb05 100644 --- a/app/client/src/comments/tests/Comments.test.tsx +++ b/app/client/src/comments/tests/Comments.test.tsx @@ -49,7 +49,7 @@ describe("Comment threads", () => { }); it("can be created", async (done) => { - const { getByDataCy, getAllByDataCy, findByDataCy, findByText } = render( + const { findByDataCy, findByText, getAllByDataCy, getByDataCy } = render(
, @@ -87,7 +87,7 @@ describe("Comment threads", () => { done(); }); it("accept replies", async (done) => { - const { getByDataCy, findByText, findByDataCy } = render( + const { findByDataCy, findByText, getByDataCy } = render(
, diff --git a/app/client/src/comments/tour/AddCommentTourComponent.tsx b/app/client/src/comments/tour/AddCommentTourComponent.tsx new file mode 100644 index 0000000000..c8bfe95bca --- /dev/null +++ b/app/client/src/comments/tour/AddCommentTourComponent.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import styled from "styled-components"; +import TourTooltipWrapper from "components/ads/tour/TourTooltipWrapper"; +import { TourType } from "entities/Tour"; + +const Dot = styled.div` + position: fixed; + top: 50%; + left: calc(125px + 50%); + width: 0px; + height: 0px; +`; + +export default function AddCommentTourComponent() { + return ( + + +
+ + + ); +} diff --git a/app/client/src/comments/tour/commentsTourSteps.ts b/app/client/src/comments/tour/commentsTourSteps.ts new file mode 100644 index 0000000000..fdf40b8bfd --- /dev/null +++ b/app/client/src/comments/tour/commentsTourSteps.ts @@ -0,0 +1,11 @@ +const steps = [ + { + id: "ENTER_COMMENTS_MODE", + data: { message: "Click on the comment icon \n to enter comments mode" }, + }, + { + id: "CREATE_UNPUBLISHED_COMMENT", + data: { message: "Click anywhere on the canvas \n and leave a comment." }, + }, +]; +export default steps; diff --git a/app/client/src/comments/utils.ts b/app/client/src/comments/utils.ts index 2beb5e03ee..fbd25f8116 100644 --- a/app/client/src/comments/utils.ts +++ b/app/client/src/comments/utils.ts @@ -1,4 +1,9 @@ import { CommentThread } from "entities/Comments/CommentsInterfaces"; +import { + BUILDER_PAGE_URL, + getApplicationViewerPageURL, +} from "constants/routes"; +import { APP_MODE } from "reducers/entityReducers/appReducer"; // used for dev export const reduceCommentsByRef = (comments: any[]) => { @@ -42,3 +47,77 @@ export const transformUnpublishCommentThreadToCreateNew = (payload: any) => { comments: [{ body: commentBody }], }; }; + +/** + * Returns the offset position relative to the container + * using the coordinates from the click event + * @param clickEvent + * @param containerRef + */ +export const getOffsetPos = ( + clickEvent: React.MouseEvent, + containerRef: HTMLDivElement, +) => { + const boundingClientRect = containerRef.getBoundingClientRect(); + const containerPosition = { + left: boundingClientRect.left, + top: boundingClientRect.top, + }; + const clickPosition = { + left: clickEvent.clientX, + top: clickEvent.clientY, + }; + + const offsetLeft = clickPosition.left - containerPosition.left; + const offsetTop = clickPosition.top - containerPosition.top; + + const offsetLeftPercent = parseFloat( + `${(offsetLeft / boundingClientRect.width) * 100}`, + ); + const offsetTopPercent = parseFloat( + `${(offsetTop / boundingClientRect.height) * 100}`, + ); + + return { + left: offsetLeftPercent, + top: offsetTopPercent, + }; +}; + +export const getCommentThreadURL = ({ + applicationId, + commentThreadId, + isResolved, + pageId, + mode = APP_MODE.PUBLISHED, +}: { + applicationId?: string; + commentThreadId: string; + isResolved?: boolean; + pageId?: string; + mode?: APP_MODE; +}) => { + const queryParams: Record = { + commentThreadId, + isCommentMode: true, + }; + + if (isResolved) { + queryParams.isResolved = true; + } + + const urlBuilder = + mode === APP_MODE.PUBLISHED + ? getApplicationViewerPageURL + : BUILDER_PAGE_URL; + + const url = new URL( + `${window.location.origin}${urlBuilder( + applicationId, + pageId, + queryParams, + )}`, + ); + + return url; +}; diff --git a/app/client/src/components/ads/Button.tsx b/app/client/src/components/ads/Button.tsx index ea97229575..f1379ff564 100644 --- a/app/client/src/components/ads/Button.tsx +++ b/app/client/src/components/ads/Button.tsx @@ -70,7 +70,7 @@ const defaultProps = { size: Size.small, isLoading: false, disabled: false, - fill: false, + fill: undefined, tag: "a", }; @@ -380,7 +380,7 @@ function TextLoadingState({ text }: { text?: string }) { return {text}; } -function IconLoadingState({ size, icon }: { size?: Size; icon?: IconName }) { +function IconLoadingState({ icon, size }: { size?: Size; icon?: IconName }) { return ; } diff --git a/app/client/src/components/ads/Callout.tsx b/app/client/src/components/ads/Callout.tsx index 2df99e39a2..9e57b0e257 100644 --- a/app/client/src/components/ads/Callout.tsx +++ b/app/client/src/components/ads/Callout.tsx @@ -73,7 +73,11 @@ Callout.defaultProps = { function Callout(props: CalloutProps) { return ( - + {props.text && props.variant !== Variant.info ? ( ) : null} diff --git a/app/client/src/components/ads/Checkbox.tsx b/app/client/src/components/ads/Checkbox.tsx index a8ab84d1ae..c9041b0aef 100644 --- a/app/client/src/components/ads/Checkbox.tsx +++ b/app/client/src/components/ads/Checkbox.tsx @@ -9,12 +9,14 @@ export type CheckboxProps = CommonComponentProps & { isDefaultChecked?: boolean; onCheckChange?: (isChecked: boolean) => void; info?: string; + backgroundColor?: string; }; const Checkmark = styled.span<{ disabled?: boolean; isChecked?: boolean; info?: string; + backgroundColor?: string; }>` position: absolute; top: ${(props) => (props.info ? "6px" : "1px")}; @@ -25,14 +27,14 @@ const Checkmark = styled.span<{ props.isChecked ? props.disabled ? props.theme.colors.checkbox.disabled - : props.theme.colors.info.main + : props.backgroundColor || props.theme.colors.info.main : "transparent"}; border: 2px solid ${(props) => props.isChecked ? props.disabled ? props.theme.colors.checkbox.disabled - : props.theme.colors.info.main + : props.backgroundColor || props.theme.colors.info.main : props.theme.colors.checkbox.unchecked}; &::after { @@ -129,6 +131,7 @@ function Checkbox(props: CheckboxProps) { type="checkbox" /> ` `; const defaultColors: string[] = [ - "rgb(3, 179, 101)", + "#03b365", "#FFC13D", "#38AFF4", "#DD4B34", "#3366FF", "#2E3D49", "#F6F7F8", + "#FFFFFF", "#231F20", ]; diff --git a/app/client/src/components/ads/DisplayImageUpload.tsx b/app/client/src/components/ads/DisplayImageUpload.tsx new file mode 100644 index 0000000000..8bd949d98f --- /dev/null +++ b/app/client/src/components/ads/DisplayImageUpload.tsx @@ -0,0 +1,198 @@ +import React, { useEffect, useState } from "react"; +import { ReactComponent as ProfileImagePlaceholder } from "assets/images/profile-placeholder.svg"; +import Uppy from "@uppy/core"; +import Dialog from "components/ads/DialogComponent"; + +import { Dashboard, useUppy } from "@uppy/react"; +import { getTypographyByKey } from "constants/DefaultTheme"; + +import styled from "styled-components"; +import ImageEditor from "@uppy/image-editor"; +import { REMOVE, createMessage } from "constants/messages"; + +import "@uppy/core/dist/style.css"; +import "@uppy/dashboard/dist/style.css"; +import "@uppy/image-editor/dist/style.css"; +import "@blueprintjs/popover2/lib/css/blueprint-popover2.css"; + +type Props = { + onChange: (file: File) => void; + onRemove?: () => void; + submit: (uppy: Uppy.Uppy) => void; + value: string; + label?: string; +}; + +const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + + & .input-component { + display: none; + } + + & .view { + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + + .image-view { + width: 80px; + height: 80px; + + display: flex; + justify-content: center; + align-items: center; + background-color: ${(props) => + props.theme.colors.displayImageUpload.background}; + border-radius: 50%; + margin-bottom: ${(props) => props.theme.spaces[7]}px; + + img { + height: 100%; + width: 100%; + border-radius: 50%; + object-fit: cover; + } + } + + .label { + ${(props) => getTypographyByKey(props, "h6")} + color: ${(props) => props.theme.colors.displayImageUpload.label}; + } + } +`; + +const defaultLabel = "Upload Display Picture"; + +export default function DisplayImageUpload({ + onChange, + onRemove, + submit, + value, +}: Props) { + const [loadError, setLoadError] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const uppy = useUppy(() => { + const uppy = Uppy({ + id: "uppy", + autoProceed: false, + allowMultipleUploads: false, + restrictions: { + maxNumberOfFiles: 1, + maxFileSize: 250000, + allowedFileTypes: [".jpg", ".jpeg", ".png"], + }, + infoTimeout: 5000, + locale: { + strings: {}, + }, + }); + + uppy.setOptions({ + locale: { + strings: { + cancel: "Cancel", + done: "Cancel", + }, + }, + }); + + uppy.use(ImageEditor, { + id: "ImageEditor", + quality: 0.8, + cropperOptions: { + viewMode: 1, + background: false, + autoCropArea: 1, + responsive: true, + }, + actions: { + revert: false, + rotate: false, + flip: false, + zoomIn: false, + zoomOut: false, + cropSquare: false, + cropWidescreen: false, + cropWidescreenVertical: false, + }, + }); + + uppy.on("file-added", (file: File) => { + onChange(file); + // TO trigger edit modal + const dashboard = uppy.getPlugin("uppy-img-upload-dashboard"); + setTimeout(() => { + (dashboard as any).openFileEditor(file); + }); + }); + + uppy.on("upload", () => { + submit(uppy); + setIsModalOpen(false); + }); + + uppy.on("file-editor:complete", (updatedFile) => { + onChange(updatedFile); + }); + + return uppy; + }); + + useEffect(() => { + if (value) setLoadError(false); + }, [value]); + + return ( + setIsModalOpen(true)}> + +
+ {!value || loadError ? ( + + ) : ( + { + setLoadError(true); + }} + onLoad={() => setLoadError(false)} + src={value} + /> + )} +
+ {(!value || loadError) && ( + {defaultLabel} + )} + {value && !loadError && ( + { + e.preventDefault(); + e.stopPropagation(); + if (onRemove) onRemove(); + }} + > + {createMessage(REMOVE)} + + )} +
+ } + > + + + + ); +} diff --git a/app/client/src/components/ads/DraggableList.tsx b/app/client/src/components/ads/DraggableList.tsx index 79a03ed66d..a14d9bc176 100644 --- a/app/client/src/components/ads/DraggableList.tsx +++ b/app/client/src/components/ads/DraggableList.tsx @@ -32,7 +32,7 @@ const updateSpringStyles = ( // Styles when items are dragged/idle const dragIdleSpringStyles = ( order: Array, - { down, originalIndex, curIndex, y, itemHeight }: SpringStyleProps, + { curIndex, down, itemHeight, originalIndex, y }: SpringStyleProps, ) => (index: number) => { // picked/dragged item style if (down && index === originalIndex) { @@ -59,7 +59,7 @@ const DraggableListWrapper = styled.div` } `; -function DraggableList({ items, ItemRenderer, onUpdate, itemHeight }: any) { +function DraggableList({ itemHeight, ItemRenderer, items, onUpdate }: any) { // order of items in the list const order = useRef(items.map((_: any, index: any) => index)); @@ -119,7 +119,7 @@ function DraggableList({ items, ItemRenderer, onUpdate, itemHeight }: any) { }} style={{ height: items.length * itemHeight }} > - {springs.map(({ zIndex, y, scale }, i) => ( + {springs.map(({ scale, y, zIndex }, i) => ( + ItemRenderer={({ index, item }: any) => renderComponent({ deleteOption, updateOption, diff --git a/app/client/src/components/ads/Dropdown.tsx b/app/client/src/components/ads/Dropdown.tsx index 77e7ce9191..8741a6a8da 100644 --- a/app/client/src/components/ads/Dropdown.tsx +++ b/app/client/src/components/ads/Dropdown.tsx @@ -1,9 +1,10 @@ -import React, { useState, useEffect, useCallback } from "react"; +import React, { ReactNode, useState, useEffect, useCallback } from "react"; import Icon, { IconName, IconSize } from "./Icon"; import { CommonComponentProps, Classes } from "./common"; import Text, { TextType } from "./Text"; import { Popover, Position } from "@blueprintjs/core"; import styled from "constants/DefaultTheme"; +import { Colors } from "constants/Colors"; export type DropdownOption = { label?: string; @@ -16,6 +17,22 @@ export type DropdownOption = { onSelect?: (value?: string) => void; }; +export interface DefaultDropDownValueNodeProps { + selected: DropdownOption; + showLabelOnly?: boolean; + isOpen?: boolean; +} + +export interface RenderDropdownOptionType { + option: DropdownOption; + optionClickHandler?: (dropdownOption: DropdownOption) => void; +} + +type RenderOption = ({ + option, + optionClickHandler, +}: RenderDropdownOptionType) => ReactNode; + export type DropdownProps = CommonComponentProps & { options: DropdownOption[]; selected: DropdownOption; @@ -25,7 +42,10 @@ export type DropdownProps = CommonComponentProps & { showLabelOnly?: boolean; optionWidth?: string; showDropIcon?: boolean; + headerLabel?: string; SelectedValueNode?: typeof DefaultDropDownValueNode; + bgColor?: string; + renderOption?: RenderOption; }; export const DropdownContainer = styled.div<{ width: string; height: string }>` @@ -38,12 +58,15 @@ const Selected = styled.div<{ isOpen: boolean; disabled?: boolean; height: string; + bgColor?: string; }>` padding: ${(props) => props.theme.spaces[2]}px ${(props) => props.theme.spaces[3]}px; background: ${(props) => props.disabled ? props.theme.colors.dropdown.header.disabledBg + : !!props.bgColor + ? props.bgColor : props.theme.colors.dropdown.header.bg}; display: flex; align-items: center; @@ -53,21 +76,31 @@ const Selected = styled.div<{ cursor: pointer; ${(props) => props.isOpen - ? `border: 1px solid ${props.theme.colors.info.main}` + ? `border: 1px solid ${ + !!props.bgColor ? props.bgColor : props.theme.colors.info.main + }` : props.disabled ? `border: 1px solid ${props.theme.colors.dropdown.header.disabledBg}` - : `border: 1px solid ${props.theme.colors.dropdown.header.bg}`}; + : `border: 1px solid ${ + !!props.bgColor + ? props.bgColor + : props.theme.colors.dropdown.header.bg + }`}; ${(props) => props.isOpen && !props.disabled ? "box-sizing: border-box" : null}; ${(props) => - props.isOpen && !props.disabled + props.isOpen && !props.disabled && !props.bgColor ? "box-shadow: 0px 0px 4px 4px rgba(203, 72, 16, 0.18)" : null}; .${Classes.TEXT} { ${(props) => props.disabled ? `color: ${props.theme.colors.dropdown.header.disabledText}` - : `color: ${props.theme.colors.dropdown.header.text}`}; + : `color: ${ + !!props.bgColor + ? Colors.WHITE + : props.theme.colors.dropdown.header.text + }`}; } `; @@ -159,6 +192,12 @@ const StyledSubText = styled(Text)` } `; +const HeaderWrapper = styled.div` + color: #6d6d6d; + font-size: 10px; + padding: 0px 7px 7px 7px; +`; + const SelectedDropDownHolder = styled.div` display: flex; align-items: center; @@ -185,17 +224,17 @@ const SelectedIcon = styled(Icon)` } `; -function DefaultDropDownValueNode({ +export function DefaultDropDownValueNode({ selected, showLabelOnly, -}: { - selected: DropdownOption; - showLabelOnly?: boolean; -}) { +}: DefaultDropDownValueNodeProps) { return ( {selected.icon ? ( - + ) : null} {showLabelOnly ? selected.label : selected.value} @@ -209,6 +248,7 @@ export default function Dropdown(props: DropdownProps) { onSelect, showDropIcon = true, SelectedValueNode = DefaultDropDownValueNode, + renderOption, } = { ...props }; const [isOpen, setIsOpen] = useState(false); const [selected, setSelected] = useState(props.selected); @@ -242,6 +282,7 @@ export default function Dropdown(props: DropdownProps) { position={Position.BOTTOM_LEFT} > setIsOpen(!isOpen)} > {showDropIcon && } + {props.headerLabel && ( + {props.headerLabel} + )} {props.options.map((option: DropdownOption, index: number) => { + if (renderOption) { + return renderOption({ + option, + optionClickHandler, + }); + } return ( { + if (isError) { + // if there is any error occurs while saving appname. + // last saved app name will be shown to user. + setValue(defaultValue); + } + }, [isError]); + useEffect(() => { setSavingState(props.savingState); }, [props.savingState]); diff --git a/app/client/src/components/ads/EmojiPicker.tsx b/app/client/src/components/ads/EmojiPicker.tsx index 7829e7ec0e..09f1937f86 100644 --- a/app/client/src/components/ads/EmojiPicker.tsx +++ b/app/client/src/components/ads/EmojiPicker.tsx @@ -1,46 +1,62 @@ import React, { useCallback, useState } from "react"; -import Picker, { IEmojiData } from "emoji-picker-react"; -import { withTheme } from "styled-components"; -import Icon, { IconSize } from "components/ads/Icon"; -import { Popover, Position } from "@blueprintjs/core"; -import { Theme } from "constants/DefaultTheme"; +import { Picker, BaseEmoji } from "emoji-mart"; +import { Popover2 } from "@blueprintjs/popover2"; +import Icon, { IconName, IconSize } from "components/ads/Icon"; + +import { withTheme } from "styled-components"; +import { Theme } from "constants/DefaultTheme"; +import "@blueprintjs/popover2/lib/css/blueprint-popover2.css"; +import "emoji-mart/css/emoji-mart.css"; -// TODO remove: (trigger tests) const EmojiPicker = withTheme( ({ - theme, + iconName, + iconSize, onSelectEmoji, + theme, }: { + iconName?: IconName; theme: Theme; - onSelectEmoji: (e: React.MouseEvent, emojiObject: IEmojiData) => void; + onSelectEmoji: (e: React.MouseEvent, emojiObject: BaseEmoji) => void; + iconSize?: IconSize; }) => { const [isOpen, setIsOpen] = useState(false); const handleSelectEmoji = useCallback( - (e: React.MouseEvent, emojiObject: IEmojiData) => { - onSelectEmoji(e, emojiObject); + (emoji, event) => { + onSelectEmoji(event, emoji); setIsOpen(false); }, [onSelectEmoji], ); return ( - + } isOpen={isOpen} minimal onInteraction={(nextOpenState) => { setIsOpen(nextOpenState); }} - position={Position.BOTTOM_RIGHT} + portalClassName="emoji-picker-portal" > - - + ); }, ); diff --git a/app/client/src/components/ads/EmojiReactions.tsx b/app/client/src/components/ads/EmojiReactions.tsx new file mode 100644 index 0000000000..cd57d6f64e --- /dev/null +++ b/app/client/src/components/ads/EmojiReactions.tsx @@ -0,0 +1,185 @@ +import React from "react"; +import styled from "styled-components"; +import EmojiPicker from "./EmojiPicker"; +import { IconSize } from "./Icon"; +import TooltipComponent from "./Tooltip"; + +const Container = styled.div` + display: flex; + flex-wrap: wrap; +`; + +const Bubble = styled.div<{ active?: boolean }>` + font-size: 16px; // emoji + cursor: pointer; + display: flex; + align-items: center; + padding: ${(props) => `2px ${props.theme.spaces[2]}px`}; + + background-color: ${(props) => + props.active + ? props.theme.colors.reactionsComponent.reactionBackgroundActive + : props.theme.colors.reactionsComponent.reactionBackground}; + + border: 1px solid + ${(props) => + props.active + ? props.theme.colors.reactionsComponent.borderActive + : "transparent"}; + + border-radius: ${(props) => `${props.theme.radii[4]}px`}; + margin-left: ${(props) => `${props.theme.radii[1]}px`}; + &:first-child { + margin-left: 0; + } +`; + +const Count = styled.div<{ active?: boolean }>` + display: inline; + color: ${(props) => + props.active + ? props.theme.colors.reactionsComponent.textActive + : props.theme.colors.reactionsComponent.text}; + margin-left: ${(props) => props.theme.spaces[1]}px; + max-width: 30px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +`; + +const ReactionsByContainer = styled.span` + max-width: 200px; + display: inline-block; +`; + +function ReactionsBy(props: { reaction: Reaction }) { + const { reaction } = props; + + if (!reaction?.users) return null; + const isSliced = reaction?.users.length > 5; + const users = reaction?.users.slice(0, 5); + + if (isSliced) users.push("..."); + if (reaction.active) users.unshift("You"); + + return {users.join(", ")}; +} + +export type Reaction = { + count: number; + reactionEmoji: string; + active?: boolean; + users?: Array; +}; + +export type Reactions = Record; + +const transformReactions = (reactions: Reactions): Array => { + return Object.keys(reactions).map((emojiReaction: string) => ({ + ...reactions[emojiReaction], + emojiReaction, + })); +}; + +export enum ReactionOperation { + ADD = "ADD", + REMOVE = "REMOVE", +} + +function EmojiReactions({ + onSelectReaction, + reactions = {}, + hideReactions = false, + iconSize = IconSize.XXL, +}: { + onSelectReaction: ( + event: React.MouseEvent, + emojiData: string, + updatedReactions: Reactions, + addOrRemove: ReactionOperation, + ) => void; + reactions?: Reactions; + hideReactions?: boolean; + iconSize?: IconSize; +}) { + const handleSelectReaction = ( + _event: React.MouseEvent, + emojiData: string, + ) => { + const reactionsObject = reactions[emojiData]; + let addOrRemove; + if (reactionsObject) { + if (reactionsObject.active) { + addOrRemove = ReactionOperation.REMOVE; + reactions[emojiData] = { + active: false, + reactionEmoji: emojiData, + count: reactionsObject.count - 1, + }; + if (reactions[emojiData].count === 0) delete reactions[emojiData]; + } else { + addOrRemove = ReactionOperation.ADD; + reactions[emojiData] = { + active: true, + reactionEmoji: emojiData, + count: reactionsObject.count + 1, + }; + } + } else { + addOrRemove = ReactionOperation.ADD; + reactions[emojiData] = { + active: true, + reactionEmoji: emojiData, + count: 1, + }; + } + onSelectReaction( + _event, + emojiData, + { ...reactions }, + addOrRemove as ReactionOperation, + ); + }; + + return ( + + {!hideReactions && + transformReactions(reactions).map((reaction: Reaction) => ( + } + disabled={!reaction.users || reaction.users.length === 0} + key={reaction.reactionEmoji} + modifiers={{ preventOverflow: { enabled: true } }} + > + handleSelectReaction(e, reaction.reactionEmoji)} + > + {reaction.reactionEmoji} + {reaction.count > 1 && ( + {reaction.count} + )} + + + ))} + {!hideReactions ? ( + + handleSelectReaction(e, emoji.native)} + /> + + ) : ( + handleSelectReaction(e, emoji.native)} + /> + )} + + ); +} + +export default EmojiReactions; diff --git a/app/client/src/components/ads/FilePicker.tsx b/app/client/src/components/ads/FilePicker.tsx index 1d5d6936b4..4ccffaa125 100644 --- a/app/client/src/components/ads/FilePicker.tsx +++ b/app/client/src/components/ads/FilePicker.tsx @@ -3,28 +3,52 @@ import styled from "styled-components"; import Button, { Category, Size } from "./Button"; import axios from "axios"; import { ReactComponent as UploadIcon } from "../../assets/icons/ads/upload.svg"; +import { ReactComponent as UploadSuccessIcon } from "../../assets/icons/ads/upload_success.svg"; import { DndProvider, useDrop, DropTargetMonitor } from "react-dnd"; import HTML5Backend, { NativeTypes } from "react-dnd-html5-backend"; import Text, { TextType } from "./Text"; import { Classes, Variant } from "./common"; import { Toaster } from "./Toast"; -import { createMessage, ERROR_FILE_TOO_LARGE } from "constants/messages"; - +import { + createMessage, + ERROR_FILE_TOO_LARGE, + REMOVE_FILE_TOOL_TIP, +} from "constants/messages"; +import TooltipComponent from "components/ads/Tooltip"; +import { Position } from "@blueprintjs/core/lib/esm/common/position"; +import Icon, { IconSize } from "./Icon"; const CLOUDINARY_PRESETS_NAME = ""; const CLOUDINARY_CLOUD_NAME = ""; +const FileEndings = { + IMAGE: ".jpeg,.png,.svg", + JSON: ".json", + TEXT: ".txt", + ANY: "*", +}; + +export enum FileType { + IMAGE = "IMAGE", + JSON = "JSON", + TEXT = "TEXT", + ANY = "ANY", +} + type FilePickerProps = { onFileUploaded?: (fileUrl: string) => void; onFileRemoved?: () => void; fileUploader?: FileUploader; url?: string; logoUploadError?: string; + fileType: FileType; + delayedUpload?: boolean; }; const ContainerDiv = styled.div<{ isUploaded: boolean; isActive: boolean; canDrop: boolean; + fileType: FileType; }>` width: 320px; height: 190px; @@ -41,7 +65,7 @@ const ContainerDiv = styled.div<{ color: ${(props) => props.theme.colors.filePicker.color}; } - .bg-image { + .upload-form-container { width: 100%; height: 100%; display: grid; @@ -51,15 +75,36 @@ const ContainerDiv = styled.div<{ background-size: contain; } + .centered { + justify-content: center; + flex-direction: column; + align-items: center; + + .success-container { + display: flex; + align-items: center; + .success-icon { + margin-right: ${(props) => props.theme.spaces[4]}px; + } + + .success-text { + color: #03b365; + margin-right: ${(props) => props.theme.spaces[4]}px; + } + } + } + .file-description { width: 95%; - margin-top: auto; + margin: 0 auto; + margin-top: ${(props) => + props.fileType === FileType.IMAGE ? "auto" : "0px"}; margin-bottom: ${(props) => props.theme.spaces[6] + 1}px; display: none; } .file-spec { - margin-bottom: ${(props) => props.theme.spaces[2]}px; + margin-bottom: ${(props) => props.theme.spaces[3]}px; span { margin-right: ${(props) => props.theme.spaces[4]}px; } @@ -116,6 +161,11 @@ const ContainerDiv = styled.div<{ } `; +const IconWrapper = styled.div` + width: ${(props) => props.theme.spaces[9]}px; + padding-left: ${(props) => props.theme.spaces[2]}px; +`; + export type SetProgress = (percentage: number) => void; export type UploadCallback = (url: string) => void; export type FileUploader = ( @@ -159,7 +209,7 @@ export function CloudinaryUploader( } function FilePickerComponent(props: FilePickerProps) { - const { logoUploadError } = props; + const { fileType, logoUploadError } = props; const [fileInfo, setFileInfo] = useState<{ name: string; size: number }>({ name: "", size: 0, @@ -207,7 +257,7 @@ function FilePickerComponent(props: FilePickerProps) { } if (uploadPercentage === 100) { setIsUploaded(true); - if (fileDescRef.current && bgRef.current) { + if (fileDescRef.current && bgRef.current && fileType === FileType.IMAGE) { fileDescRef.current.style.display = "none"; bgRef.current.style.opacity = "1"; } @@ -219,6 +269,35 @@ function FilePickerComponent(props: FilePickerProps) { } function handleFileUpload(files: FileList | null) { + if (fileType === FileType.IMAGE) { + handleImageFileUpload(files); + } else { + handleOtherFileUpload(files); + } + } + + function handleOtherFileUpload(files: FileList | null) { + const file = files && files[0]; + let fileSize = 0; + if (!file) { + return; + } + fileSize = Math.floor(file.size / 1024); + setFileInfo({ name: file.name, size: fileSize }); + if (props.delayedUpload) { + setIsUploaded(true); + setProgress(100); + } + if (fileDescRef.current) { + fileDescRef.current.style.display = "flex"; + } + if (fileContainerRef.current) { + fileContainerRef.current.style.display = "none"; + } + props.fileUploader && props.fileUploader(file, setProgress, onUpload); + } + + function handleImageFileUpload(files: FileList | null) { const file = files && files[0]; let fileSize = 0; @@ -253,10 +332,15 @@ function FilePickerComponent(props: FilePickerProps) { } function removeFile() { - if (fileContainerRef.current && bgRef.current) { + if (fileContainerRef.current) { setFileUrl(""); + if (fileDescRef.current) { + fileDescRef.current.style.display = "none"; + } fileContainerRef.current.style.display = "flex"; - bgRef.current.style.backgroundImage = "url('')"; + if (bgRef.current) { + bgRef.current.style.backgroundImage = "url('')"; + } setIsUploaded(false); props.onFileRemoved && props.onFileRemoved(); } @@ -275,8 +359,9 @@ function FilePickerComponent(props: FilePickerProps) { } }, [props.url]); + // Following hook should be used only if file type is image. useEffect(() => { - if (fileUrl && !isUploaded) { + if (fileUrl && !isUploaded && fileType === FileType.IMAGE) { setIsUploaded(true); if (bgRef.current) { bgRef.current.style.backgroundImage = `url(${fileUrl})`; @@ -291,42 +376,47 @@ function FilePickerComponent(props: FilePickerProps) { } }, [fileUrl, logoUploadError]); - return ( - -
-
- - - Drag & Drop files to upload or - -
- handleFileUpload(el.target.files)} - ref={inputRef} - type="file" - value={""} - /> -
+ // + + const uploadFileForm = ( +
+ + + Drag & Drop files to upload or + +
+ handleFileUpload(el.target.files)} + ref={inputRef} + type="file" + value={""} + /> +
+ ); + + const uploadStatus = ( +
+ {fileInfo.name} + {fileInfo.size}KB +
+ ); + + const imageUploadComponent = ( + <> +
+ {uploadFileForm}
-
- {fileInfo.name} - {fileInfo.size}KB -
+ {uploadStatus}
@@ -341,6 +431,45 @@ function FilePickerComponent(props: FilePickerProps) { text="remove" />
+ + ); + + const uploadComponent = ( +
+ {uploadFileForm} +
+ {uploadStatus} +
+ + + Successfully Uploaded! + + + removeFile()}> + + + +
+
+
+ ); + + return ( + + {fileType === FileType.IMAGE ? imageUploadComponent : uploadComponent} ); } diff --git a/app/client/src/components/ads/Icon.tsx b/app/client/src/components/ads/Icon.tsx index 17c778094a..109f5a704e 100644 --- a/app/client/src/components/ads/Icon.tsx +++ b/app/client/src/components/ads/Icon.tsx @@ -3,6 +3,7 @@ import { ReactComponent as DeleteIcon } from "assets/icons/ads/delete.svg"; import { ReactComponent as BookIcon } from "assets/icons/ads/book.svg"; import { ReactComponent as BugIcon } from "assets/icons/ads/bug.svg"; import { ReactComponent as CancelIcon } from "assets/icons/ads/cancel.svg"; +import { ReactComponent as ExpandMore } from "assets/icons/ads/expand-more.svg"; import { ReactComponent as CrossIcon } from "assets/icons/ads/cross.svg"; import { ReactComponent as OpenIcon } from "assets/icons/ads/open.svg"; import { ReactComponent as UserIcon } from "assets/icons/ads/user.svg"; @@ -39,6 +40,7 @@ import { ReactComponent as NoResponseIcon } from "assets/icons/ads/no-response.s import { ReactComponent as LightningIcon } from "assets/icons/ads/lightning.svg"; import { ReactComponent as AddMoreIcon } from "assets/icons/ads/add-more.svg"; import { ReactComponent as RightArrowIcon } from "assets/icons/ads/right-arrow.svg"; +import { ReactComponent as TrendingFlat } from "assets/icons/ads/trending-flat.svg"; import { ReactComponent as DatasourceIcon } from "assets/icons/ads/datasource.svg"; import { ReactComponent as PlayIcon } from "assets/icons/ads/play.svg"; import { ReactComponent as DesktopIcon } from "assets/icons/ads/desktop.svg"; @@ -53,8 +55,24 @@ import { ReactComponent as Pin } from "assets/icons/comments/pin.svg"; import { ReactComponent as OvalCheck } from "assets/icons/comments/check-oval.svg"; import { ReactComponent as ContextMenu } from "assets/icons/ads/context-menu.svg"; import { ReactComponent as Trash } from "assets/icons/comments/trash.svg"; -import { ReactComponent as Pin2 } from "assets/icons/comments/pin_2.svg"; +import { ReactComponent as ReadPin } from "assets/icons/comments/read-pin.svg"; +import { ReactComponent as UnreadPin } from "assets/icons/comments/unread-pin.svg"; import { ReactComponent as Link2 } from "assets/icons/comments/link.svg"; +import { ReactComponent as CommentContextMenu } from "assets/icons/comments/context-menu.svg"; +import { ReactComponent as DownArrow2 } from "assets/icons/comments/down-arrow.svg"; +import { ReactComponent as Filter } from "assets/icons/comments/filter.svg"; +import { ReactComponent as Chat } from "assets/icons/comments/chat.svg"; +import { ReactComponent as Pin3 } from "assets/icons/comments/pin_3.svg"; +import { ReactComponent as Unpin } from "assets/icons/comments/unpin.svg"; +import { ReactComponent as Reaction } from "assets/icons/comments/reaction.svg"; +import { ReactComponent as Reaction2 } from "assets/icons/comments/reaction-2.svg"; +import { ReactComponent as Upload } from "assets/icons/ads/upload.svg"; +import { ReactComponent as Download } from "assets/icons/ads/download.svg"; +import { ReactComponent as ArrowForwardIcon } from "assets/icons/control/arrow_forward.svg"; +import { ReactComponent as CapSolidIcon } from "assets/icons/control/cap_solid.svg"; +import { ReactComponent as CapDotIcon } from "assets/icons/control/cap_dot.svg"; +import { ReactComponent as LineDottedIcon } from "assets/icons/control/line_dotted.svg"; +import { ReactComponent as LineDashedIcon } from "assets/icons/control/line_dashed.svg"; import styled from "styled-components"; import { CommonComponentProps, Classes } from "./common"; import { noop } from "lodash"; @@ -108,11 +126,14 @@ export const sizeHandler = (size?: IconSize) => { }; export const IconCollection = [ + "upload", + "download", "book", "bug", "cancel", "cross", "delete", + "expand-more", "open", "user", "general", @@ -149,6 +170,7 @@ export const IconCollection = [ "lightning", "add-more", "right-arrow", + "trending-flat", "datasource", "play", "desktop", @@ -168,9 +190,23 @@ export const IconCollection = [ "PARAGRAPH_TWO", "context-menu", "trash", - "pin-2", "link-2", "close-x", + "comment-context-menu", + "down-arrow-2", + "read-pin", + "unread-pin", + "filter", + "chat", + "pin-3", + "unpin", + "reaction", + "reaction-2", + "arrow-forward", + "cap-solid", + "cap-dot", + "line-dotted", + "line-dashed", ] as const; export type IconName = typeof IconCollection[number]; @@ -203,17 +239,22 @@ export const IconWrapper = styled.span` !props.keepColors ? ` path { - fill: ${props.theme.colors.icon.hover}; + fill: ${props.hoverFillColor || props.theme.colors.icon.hover}; } ` : ""} } - &:active { + &:hover { cursor: pointer; + ${(props) => + !props.keepColors + ? ` path { - fill: ${(props) => props.theme.colors.icon.active}; + fill: ${props.hoverFillColor || props.theme.colors.icon.hover}; } + ` + : ""} } `; @@ -224,6 +265,7 @@ export type IconProps = { className?: string; onClick?: (e: React.MouseEvent) => void; fillColor?: string; + hoverFillColor?: string; keepColors?: boolean; }; @@ -246,6 +288,9 @@ const Icon = forwardRef( case "delete": returnIcon = ; break; + case "expand-more": + returnIcon = ; + break; case "open": returnIcon = ; break; @@ -358,6 +403,9 @@ const Icon = forwardRef( case "right-arrow": returnIcon = ; break; + case "trending-flat": + returnIcon = ; + break; case "datasource": returnIcon = ; break; @@ -405,8 +453,12 @@ const Icon = forwardRef( returnIcon = ; break; - case "pin-2": - returnIcon = ; + case "read-pin": + returnIcon = ; + break; + + case "unread-pin": + returnIcon = ; break; case "link-2": @@ -417,6 +469,66 @@ const Icon = forwardRef( returnIcon = ; break; + case "comment-context-menu": + returnIcon = ; + break; + + case "down-arrow-2": + returnIcon = ; + break; + + case "filter": + returnIcon = ; + break; + + case "chat": + returnIcon = ; + break; + + case "pin-3": + returnIcon = ; + break; + + case "unpin": + returnIcon = ; + break; + + case "reaction": + returnIcon = ; + break; + + case "reaction-2": + returnIcon = ; + break; + + case "upload": + returnIcon = ; + break; + + case "download": + returnIcon = ; + break; + + case "arrow-forward": + returnIcon = ; + break; + + case "cap-solid": + returnIcon = ; + break; + + case "cap-dot": + returnIcon = ; + break; + + case "line-dotted": + returnIcon = ; + break; + + case "line-dashed": + returnIcon = ; + break; + default: returnIcon = null; break; diff --git a/app/client/src/components/ads/IconTabsComponent.tsx b/app/client/src/components/ads/IconTabsComponent.tsx index 0ea7d8e293..a4c7909255 100644 --- a/app/client/src/components/ads/IconTabsComponent.tsx +++ b/app/client/src/components/ads/IconTabsComponent.tsx @@ -3,7 +3,8 @@ import styled from "styled-components"; import { ControlIcons, ControlIconName } from "icons/ControlIcons"; const ItemWrapper = styled.div<{ selected: boolean }>` - width: 32px; + width: auto; + padding: 0 5px; height: 32px; display: flex; align-items: center; @@ -30,6 +31,7 @@ const FlexWrapper = styled.div` export interface IconTabOption { icon: string; value: string; + width?: number; } interface IconTabsComponentProps { @@ -41,21 +43,23 @@ interface IconTabsComponentProps { function IconTabsComponent(props: IconTabsComponentProps) { return ( - {props.options.map((option: IconTabOption, index: number) => { - const controlIconName: ControlIconName = option.icon; - const ControlIcon = ControlIcons[controlIconName]; - const isSelected = props.value === option.value; - return ( - props.selectOption(option.value)} - selected={isSelected} - > - - - ); - })} + {props.options.map( + ({ icon, value, width = 24 }: IconTabOption, index: number) => { + const controlIconName: ControlIconName = icon; + const ControlIcon = ControlIcons[controlIconName]; + const isSelected = props.value === value; + return ( + props.selectOption(value)} + selected={isSelected} + > + + + ); + }, + )} ); } diff --git a/app/client/src/components/ads/MentionsInput.tsx b/app/client/src/components/ads/MentionsInput.tsx index 94525cb16f..18640a24db 100644 --- a/app/client/src/components/ads/MentionsInput.tsx +++ b/app/client/src/components/ads/MentionsInput.tsx @@ -1,16 +1,126 @@ import React, { useCallback, useMemo, useRef, useState } from "react"; import { DraftHandleValue, EditorState } from "draft-js"; import Editor from "@draft-js-plugins/editor"; +import ProfileImage, { Profile } from "pages/common/ProfileImage"; + import createMentionPlugin, { MentionData } from "@draft-js-plugins/mention"; import styled from "styled-components"; import "@draft-js-plugins/mention/lib/plugin.css"; import "draft-js/dist/Draft.css"; +import { getTypographyByKey } from "constants/DefaultTheme"; +import { EntryComponentProps } from "@draft-js-plugins/mention/lib/MentionSuggestions/Entry/Entry"; + +import Icon from "components/ads/Icon"; + +import { INVITE_A_NEW_USER, createMessage } from "constants/messages"; +import { USER_PHOTO_URL } from "constants/userConstants"; + +const StyledMention = styled.span` + color: ${(props) => props.theme.colors.comments.mention}; +`; + +export function MentionComponent(props: { + children: React.ReactNode; + entityKey: string; +}) { + const { children } = props; + return @{children}; +} + +export enum Trigger { + "@" = "@", + "+" = "+", +} + +const StyledSuggestionsComponent = styled.div<{ isFocused?: boolean }>` + display: flex; + padding: ${(props) => + `${props.theme.spaces[4]}px ${props.theme.spaces[6]}px`}; + + & ${Profile} { + margin-right: ${(props) => props.theme.spaces[4]}px; + } + align-items: center; + + &:hover { + background-color: ${(props) => props.theme.colors.mentionSuggestion.hover}; + } + + ${(props) => + props.isFocused + ? ` + background-color: ${props.theme.colors.mentionSuggestion.hover}; + ` + : ""} +`; + +const Name = styled.div` + ${(props) => getTypographyByKey(props, "h5")} + color: ${(props) => props.theme.colors.mentionSuggestion.nameText}; +`; + +const Username = styled.div` + ${(props) => getTypographyByKey(props, "p3")} + color: ${(props) => props.theme.colors.mentionSuggestion.usernameText}; +`; + +const PlusCircle = styled.div` + width: 25px; + height: 25px; + display: flex; + border-radius: 50%; + align-items: center; + justify-content: center; + background-color: ${(props) => + props.theme.colors.mentionsInput.mentionsInviteBtnPlusIcon}; + & svg path { + stroke: #fff; + } + margin-right: ${(props) => props.theme.spaces[4]}px; +`; + +function SuggestionComponent(props: EntryComponentProps) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { theme, ...parentProps } = props; + const { user } = props.mention; + + if (props.mention?.isInviteTrigger) { + return ( + + + + +
+ {createMessage(INVITE_A_NEW_USER)} + {props.mention.name} +
+
+ ); + } + + return ( + + +
+ {props.mention.name} + {user?.username} +
+
+ ); +} const StyledContainer = styled.div` - max-height: 60px; overflow: auto; flex: 1; + + & [id^="mentions-list"] { + padding: 0; + } `; type Props = { @@ -19,24 +129,30 @@ type Props = { editorState: EditorState; setEditorState: (editorState: EditorState) => void; readOnly?: boolean; - onSearchSuggestions: ({ value }: { value: string }) => void; + onSearchSuggestions: (props: { value: string; trigger: string }) => void; autoFocus: boolean; placeholder?: string; + onAddMention?: (mention: MentionData) => void; }; function MentionsInput({ - onSubmit, - suggestions, - onSearchSuggestions, - editorState, - setEditorState, autoFocus, + editorState, + onAddMention, + onSearchSuggestions, + onSubmit, placeholder, + setEditorState, + suggestions, }: Props) { const ref = useRef(null); const [open, setOpen] = useState(false); const { MentionSuggestions, plugins } = useMemo(() => { - const mentionPlugin = createMentionPlugin({ mentionTrigger: "@" }); + const mentionPlugin = createMentionPlugin({ + mentionRegExp: "[\\w@\\._]", + mentionTrigger: ["@", "+"], + mentionComponent: MentionComponent, + }); const { MentionSuggestions } = mentionPlugin; return { plugins: [mentionPlugin], MentionSuggestions }; }, []); @@ -79,13 +195,16 @@ function MentionsInput({ ref={setRef} /> { + if (onAddMention) { + onAddMention(mention); + } + }} onOpenChange={onOpenChange} onSearchChange={onSearchSuggestions} open={open} suggestions={suggestions} - // onAddMention={() => { - // // get the mention object selected - // }} /> ); diff --git a/app/client/src/components/ads/Menu.tsx b/app/client/src/components/ads/Menu.tsx index dc3437cba9..b235273c38 100644 --- a/app/client/src/components/ads/Menu.tsx +++ b/app/client/src/components/ads/Menu.tsx @@ -12,6 +12,8 @@ type MenuProps = CommonComponentProps & { onOpening?: (node: HTMLElement) => void; onClosing?: (node: HTMLElement) => void; modifiers?: PopperModifiers; + isOpen?: boolean; + onClose?: () => void; }; const MenuWrapper = styled.div` @@ -30,8 +32,10 @@ function Menu(props: MenuProps) { className={props.className} data-cy={props.cypressSelector} disabled={props.disabled} + isOpen={props.isOpen} minimal modifiers={props.modifiers} + onClose={props.onClose} onClosing={props.onClosing} onOpening={props.onOpening} portalClassName={props.className} diff --git a/app/client/src/components/ads/Radio.tsx b/app/client/src/components/ads/Radio.tsx index 2d89c7e2d3..cbb38db562 100644 --- a/app/client/src/components/ads/Radio.tsx +++ b/app/client/src/components/ads/Radio.tsx @@ -15,6 +15,7 @@ export type RadioProps = CommonComponentProps & { defaultValue: string; onSelect?: (value: string) => void; options: OptionProps[]; + backgroundColor?: string; }; const RadioGroup = styled.div<{ @@ -35,10 +36,11 @@ const RadioGroup = styled.div<{ : null}; `; -const Radio = styled.label<{ +export const Radio = styled.label<{ disabled?: boolean; columns?: number; rows?: number; + backgroundColor?: string; }>` display: block; position: relative; @@ -100,7 +102,8 @@ const Radio = styled.label<{ ${(props) => props.disabled ? `background-color: ${props.theme.colors.radio.disabled}` - : `background-color: ${props.theme.colors.info.main};`}; + : `background-color: ${props.backgroundColor || + props.theme.colors.info.main};`}; top: ${(props) => props.theme.spaces[1] - 2}px; left: ${(props) => props.theme.spaces[1] - 2}px; border-radius: 50%; @@ -137,6 +140,7 @@ export default function RadioComponent(props: RadioProps) { > {props.options.map((option: OptionProps, index: number) => ( props.theme.spaces[7]}px; + justify-content: space-between; + display: flex; +`; + +const Dot = styled.div<{ active: boolean }>` + width: 5px; + height: 5px; + border-radius: 50%; + margin-right: ${(props) => props.theme.spaces[1]}px; + background-color: ${(props) => + props.active + ? props.theme.colors.showcaseCarousel.activeStepDot + : props.theme.colors.showcaseCarousel.inactiveStepDot}; +`; + +const Row = styled.div` + display: flex; + align-items: center; +`; + +const Buttons = styled.div` + display: flex; + & button:last-child { + margin-left: ${(props) => props.theme.spaces[1]}px; +`; + +type Step = { + component: any; + props: any; +}; + +export type Steps = Array; + +type Props = { + steps: Steps; +}; + +type DotsProps = { + count: number; + activeIndex: number; +}; + +function Dots(props: DotsProps) { + return ( + + {Array.from(new Array(props.count)).map((_a, index) => ( + + ))} + + ); +} + +export default function ShowcaseCarousel(props: Props) { + const { steps } = props; + const [activeIndex, setCurrentIdx] = useState(0); + const currentStep = steps[activeIndex]; + const { component: ContentComponent, props: componentProps } = currentStep; + const length = steps.length; + + const transition = useTransition("key", null, { + from: { transform: "translateY(+2%)" }, + enter: { transform: "translateY(0%)" }, + leave: { transform: "translateY(0%)" }, + config: { duration: 300 }, + }); + + return ( + + {transition.map( + ({ item, props: springProps }: { item: string; props: any }) => ( + + + + ), + )} +
+ + + {!componentProps.hideBackBtn && ( +
+
+ ); +} diff --git a/app/client/src/components/ads/Table.tsx b/app/client/src/components/ads/Table.tsx index fdfd1501be..2c1f3d8e5c 100644 --- a/app/client/src/components/ads/Table.tsx +++ b/app/client/src/components/ads/Table.tsx @@ -93,14 +93,14 @@ interface TableProps { } function Table(props: TableProps) { - const { data, columns } = props; + const { columns, data } = props; const { - getTableProps, getTableBodyProps, + getTableProps, headerGroups, - rows, prepareRow, + rows, } = useTable({ columns, data }, useSortBy); return ( diff --git a/app/client/src/components/ads/Tabs.tsx b/app/client/src/components/ads/Tabs.tsx index a70206d531..2a52390282 100644 --- a/app/client/src/components/ads/Tabs.tsx +++ b/app/client/src/components/ads/Tabs.tsx @@ -11,10 +11,13 @@ export type TabProp = { count?: number; panelComponent: JSX.Element; icon?: IconName; + iconSize?: IconSize; }; -const TabsWrapper = styled.div<{ shouldOverflow?: boolean }>` - user-select: none; +const TabsWrapper = styled.div<{ + shouldOverflow?: boolean; + vertical?: boolean; +}>` border-radius: 0px; height: 100%; .react-tabs { @@ -27,9 +30,9 @@ const TabsWrapper = styled.div<{ shouldOverflow?: boolean }>` .react-tabs__tab-list { margin: 0px; display: flex; - align-items: center; - border-bottom: ${(props) => props.theme.spaces[1] - 2}px solid - ${(props) => props.theme.colors.tabs.border}; + flex-direction: ${(props) => (!!props.vertical ? "column" : "row")}; + align-items: ${(props) => (!!props.vertical ? "flex-start" : "center")}; + border-bottom: none; color: ${(props) => props.theme.colors.tabs.normal}; path { fill: ${(props) => props.theme.colors.tabs.normal}; @@ -46,8 +49,10 @@ const TabsWrapper = styled.div<{ shouldOverflow?: boolean }>` display: flex; align-items: center; justify-content: flex-start; - padding: ${(props) => props.theme.spaces[3] - 1}px 0 - ${(props) => props.theme.spaces[4]}px 0; + padding: ${(props) => props.theme.spaces[3] - 1}px + ${(props) => (props.vertical ? `${props.theme.spaces[4] - 1}px` : 0)} + ${(props) => props.theme.spaces[4]}px + ${(props) => (props.vertical ? `${props.theme.spaces[4] - 1}px` : 0)}; margin-right: ${(props) => props.theme.spaces[12] - 3}px; text-align: center; display: inline-flex; @@ -73,10 +78,15 @@ const TabsWrapper = styled.div<{ shouldOverflow?: boolean }>` &::after { content: ""; position: absolute; - width: 100%; - bottom: ${(props) => props.theme.spaces[0] - 1}px; + width: ${(props) => + props.vertical ? `${props.theme.spaces[1] - 2}px` : "100%"}; + bottom: ${(props) => + props.vertical ? "0%" : `${props.theme.spaces[0] - 1}px`}; + top: ${(props) => + props.vertical ? `${props.theme.spaces[0] - 1}px` : "100%"}; left: ${(props) => props.theme.spaces[0]}px; - height: ${(props) => props.theme.spaces[1] - 2}px; + height: ${(props) => + props.vertical ? "100%" : `${props.theme.spaces[1] - 2}px`}; background-color: ${(props) => props.theme.colors.info.main}; } } @@ -84,10 +94,15 @@ const TabsWrapper = styled.div<{ shouldOverflow?: boolean }>` &::after { content: ""; position: absolute; - width: 100%; - bottom: ${(props) => props.theme.spaces[0] - 1}px; + width: ${(props) => + props.vertical ? `${props.theme.spaces[1] - 2}px` : "100%"}; + bottom: ${(props) => + props.vertical ? "0%" : `${props.theme.spaces[0] - 1}px`}; + top: ${(props) => + props.vertical ? `${props.theme.spaces[0] - 1}px` : "100%"}; left: ${(props) => props.theme.spaces[0]}px; - height: ${(props) => props.theme.spaces[1] - 2}px; + height: ${(props) => + props.vertical ? "100%" : `${props.theme.spaces[1] - 2}px`}; background-color: ${(props) => props.theme.colors.info.main}; } box-shadow: none; @@ -102,19 +117,19 @@ const TabTitleWrapper = styled.div` display: flex; align-items: center; .${Classes.ICON} { - margin-right: ${(props) => props.theme.spaces[3]}px; + margin-right: ${(props) => props.theme.spaces[2]}px; } `; -const TabTitle = styled.span` +export const TabTitle = styled.span` font-size: ${(props) => props.theme.typography.h5.fontSize}px; font-weight: ${(props) => props.theme.typography.h5.fontWeight}; line-height: ${(props) => props.theme.typography.h5.lineHeight - 3}px; letter-spacing: ${(props) => props.theme.typography.h5.letterSpacing}px; - margin: 0 5px; + margin: 0; `; -const TabCount = styled.div` +export const TabCount = styled.div` background-color: ${(props) => props.theme.colors.tabs.countBg}; border-radius: 8px; width: 17px; @@ -128,6 +143,7 @@ type TabbedViewComponentType = CommonComponentProps & { selectedIndex?: number; onSelect?: (tabIndex: number) => void; overflow?: boolean; + vertical?: boolean; }; export function TabComponent(props: TabbedViewComponentType) { @@ -135,6 +151,7 @@ export function TabComponent(props: TabbedViewComponentType) { { @@ -144,10 +161,13 @@ export function TabComponent(props: TabbedViewComponentType) { > {props.tabs.map((tab) => ( - + {tab.icon ? ( - + ) : null} {tab.title} {tab.count && tab.count > 0 ? ( diff --git a/app/client/src/components/ads/Text.tsx b/app/client/src/components/ads/Text.tsx index c2a58a9a06..e2a3a526bb 100644 --- a/app/client/src/components/ads/Text.tsx +++ b/app/client/src/components/ads/Text.tsx @@ -32,6 +32,7 @@ export type TextProps = CommonComponentProps & { className?: string; weight?: FontWeight; highlight?: boolean; + textAlign?: string; }; const typeSelector = (props: TextProps & ThemeProp): string => { @@ -72,6 +73,7 @@ const Text = styled.span.attrs((props: TextProps) => ({ color: ${(props) => props.highlight ? props.theme.colors.text.highlight : typeSelector(props)}; text-transform: ${(props) => (props.case ? props.case : "none")}; + text-align: ${(props) => (props.textAlign ? props.textAlign : "normal")}; `; export default Text; diff --git a/app/client/src/components/ads/TextInput.tsx b/app/client/src/components/ads/TextInput.tsx index 633528236b..4fc64505d0 100644 --- a/app/client/src/components/ads/TextInput.tsx +++ b/app/client/src/components/ads/TextInput.tsx @@ -84,7 +84,7 @@ const boxStyles = ( const StyledInput = styled((props) => { // we are removing non input related props before passing them in the components // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { inputStyle, inputRef, dataType, theme, ...inputProps } = props; + const { dataType, inputRef, inputStyle, theme, ...inputProps } = props; return props.asyncControl ? ( void; dispatchableAction?: { type: ReduxActionType; payload: any }; + showDebugButton?: boolean; hideProgressBar?: boolean; }; @@ -36,7 +37,7 @@ const WrappedToastContainer = styled.div` margin-bottom: ${(props) => props.theme.spaces[4]}px; } .Toastify__toast-container--top-right { - top: 4em; + top: 8em; } `; export function StyledToastContainer(props: ToastOptions) { @@ -96,6 +97,7 @@ const ToastBody = styled.div<{ color: ${props.theme.colors.toast.undo}; line-height: 18px; font-weight: 600; + white-space: nowrap } ` : null} @@ -131,7 +133,7 @@ function ToastComponent(props: ToastProps & { undoAction?: () => void }) { ) : null}
{props.text} - {props.variant === Variant.danger ? ( + {props.variant === Variant.danger && props.showDebugButton ? ( ) : null}
diff --git a/app/client/src/components/ads/Tooltip.tsx b/app/client/src/components/ads/Tooltip.tsx index da2b2d7b23..ce1c37a1b8 100644 --- a/app/client/src/components/ads/Tooltip.tsx +++ b/app/client/src/components/ads/Tooltip.tsx @@ -2,13 +2,15 @@ import React from "react"; import { CommonComponentProps } from "./common"; import { Position, Tooltip, PopperBoundary } from "@blueprintjs/core"; import { GLOBAL_STYLE_TOOLTIP_CLASSNAME } from "globalStyles/tooltip"; +import { Modifiers } from "popper.js"; type Variant = "dark" | "light"; type TooltipProps = CommonComponentProps & { content: JSX.Element | string; + disabled?: boolean; position?: Position; - children: JSX.Element; + children: JSX.Element | React.ReactNode; variant?: Variant; maxWidth?: string; boundary?: PopperBoundary; @@ -17,21 +19,29 @@ type TooltipProps = CommonComponentProps & { autoFocus?: boolean; hoverOpenDelay?: number; minimal?: boolean; + modifiers?: Modifiers; + isOpen?: boolean; }; +const portalContainer = document.getElementById("tooltip-root"); + function TooltipComponent(props: TooltipProps) { return ( diff --git a/app/client/src/components/ads/TreeDropdown.tsx b/app/client/src/components/ads/TreeDropdown.tsx index bede261783..be5315306c 100644 --- a/app/client/src/components/ads/TreeDropdown.tsx +++ b/app/client/src/components/ads/TreeDropdown.tsx @@ -19,6 +19,7 @@ export type TreeDropdownOption = DropdownOption & { children?: TreeDropdownOption[]; className?: string; type?: string; + icon?: React.ReactNode; }; type Setter = (value: TreeDropdownOption, defaultVal?: string) => void; @@ -137,13 +138,13 @@ function getSelectedOption( export default function TreeDropdown(props: TreeDropdownProps) { const { - selectedValue, defaultText, - optionTree, - onSelect, - getDefaults, - selectedLabelModifier, displayValue, + getDefaults, + onSelect, + optionTree, + selectedLabelModifier, + selectedValue, toggle, } = props; const selectedOption = getSelectedOption( diff --git a/app/client/src/components/ads/UserProfileImagePicker.tsx b/app/client/src/components/ads/UserProfileImagePicker.tsx new file mode 100644 index 0000000000..72a50becb8 --- /dev/null +++ b/app/client/src/components/ads/UserProfileImagePicker.tsx @@ -0,0 +1,54 @@ +import React, { useState, useRef, useEffect } from "react"; +import { updatePhoto, removePhoto } from "actions/userActions"; +import { useDispatch } from "react-redux"; + +import DisplayImageUpload from "components/ads/DisplayImageUpload"; + +import UserApi from "api/UserApi"; +import Uppy from "@uppy/core"; + +function FormDisplayImage() { + const [file, setFile] = useState(); + const [imageURL, setImageURL] = useState(`/api/${UserApi.photoURL}`); + const dispatch = useDispatch(); + const dispatchActionRef = useRef<(uppy: Uppy.Uppy) => void | null>(); + + const onUploadComplete = (uppy: Uppy.Uppy) => { + uppy.reset(); + setImageURL(`/api/${UserApi.photoURL}?${new Date().getTime()}`); + }; + + const onSelectFile = (file: any) => { + setFile(file.data); + }; + + useEffect(() => { + dispatchActionRef.current = (uppy: Uppy.Uppy) => { + dispatch(updatePhoto({ file, callback: () => onUploadComplete(uppy) })); + }; + }, [file]); + + const upload = (uppy: Uppy.Uppy) => { + if (typeof dispatchActionRef.current === "function") + dispatchActionRef.current(uppy); + }; + + const removeProfileImage = () => { + dispatch( + removePhoto(() => { + setImageURL(`/api/${UserApi.photoURL}?${new Date().getTime()}`); + }), + ); + }; + + return ( + + ); +} + +export default FormDisplayImage; diff --git a/app/client/src/components/ads/formFields/TextField.tsx b/app/client/src/components/ads/formFields/TextField.tsx index 38df047f1d..1e1b62ab36 100644 --- a/app/client/src/components/ads/formFields/TextField.tsx +++ b/app/client/src/components/ads/formFields/TextField.tsx @@ -19,7 +19,9 @@ const renderComponent = ( return ( <> - + {!componentProps.hideErrorMessage && ( + + )} ); }; @@ -32,6 +34,7 @@ type FormTextFieldProps = { intent?: Intent; disabled?: boolean; autoFocus?: boolean; + hideErrorMessage?: boolean; }; function FormTextField(props: FormTextFieldProps) { diff --git a/app/client/src/components/ads/tour/TourTooltipWrapper.tsx b/app/client/src/components/ads/tour/TourTooltipWrapper.tsx new file mode 100644 index 0000000000..abd14b7a81 --- /dev/null +++ b/app/client/src/components/ads/tour/TourTooltipWrapper.tsx @@ -0,0 +1,110 @@ +import React, { useEffect, useRef } from "react"; +import TooltipComponent from "components/ads/Tooltip"; +import { useSelector } from "react-redux"; +import Text, { TextType } from "../Text"; +import { Position } from "@blueprintjs/core"; +import { getActiveTourIndex, getActiveTourType } from "selectors/tourSelectors"; +import { TourType } from "entities/Tour"; +import TourStepsByType from "constants/TourSteps"; +import { AppState } from "reducers"; +import { noop } from "lodash"; +import styled, { CSSProperties } from "styled-components"; +import { Modifiers } from "popper.js"; +import lottie from "lottie-web"; +import pulsatingDot from "assets/lottie/pulse-dot.json"; +import { Indices } from "constants/Layers"; + +type Props = { + children: React.ReactNode; + hasOverlay?: boolean; + tourType: TourType; + tourIndex: number; + modifiers?: Modifiers; + onClick?: () => void; + pulseStyles?: CSSProperties; + showPulse?: boolean; +}; + +const Overlay = styled.div` + background-color: ${(props) => props.theme.colors.overlayColor}; + height: 100%; + width: 100%; + left: 0; + top: 0; + position: fixed; + z-index: ${Indices.Layer1}; +`; + +const PulseDot = styled.div` + position: absolute; + height: 50px; + width: 50px; +`; + +const Container = styled.div` + position: relative; + z-index: ${Indices.Layer1}; +`; + +function TourTooltipWrapper(props: Props) { + const { children, tourIndex, tourType } = props; + const isCurrentStepActive = useSelector( + (state: AppState) => getActiveTourIndex(state) === tourIndex, + ); + const isCurrentTourActive = useSelector( + (state: AppState) => getActiveTourType(state) === tourType, + ); + const tourStepsConfig = TourStepsByType[tourType as TourType]; + const tourStepConfig = tourStepsConfig[tourIndex]; + const isOpen = isCurrentStepActive && isCurrentTourActive; + const dotRef = useRef(null); + + useEffect(() => { + const anim = lottie.loadAnimation({ + animationData: pulsatingDot, + autoplay: true, + container: dotRef?.current as HTMLDivElement, + renderer: "svg", + loop: true, + }); + + return () => { + anim?.destroy(); + }; + }, [isOpen, dotRef?.current]); + + return ( + <> + {/* A crude overlay which won't work with containers having overflow hidden */} + {isOpen && props.hasOverlay && } + + {isOpen && props.showPulse && ( + + )} + + {tourStepConfig?.data.message} + + } + isOpen={!!isOpen} + modifiers={props.modifiers} + position={Position.BOTTOM} + > + {children} + + + + ); +} + +export default TourTooltipWrapper; diff --git a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx index 43b3bf3b7b..c9b30a9583 100644 --- a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx @@ -72,7 +72,7 @@ class ChartComponent extends React.Component { chartInstance = new FusionCharts(); getChartType = () => { - const { chartType, allowHorizontalScroll, chartData } = this.props; + const { allowHorizontalScroll, chartData, chartType } = this.props; const dataLength = Object.keys(chartData).length; const isMSChart = dataLength > 1; switch (chartType) { @@ -307,6 +307,15 @@ class ChartComponent extends React.Component { renderAt: this.props.widgetId + "chart-container", width: "100%", height: "100%", + events: { + dataPlotClick: (evt: any) => { + const data = evt.data; + this.props.onDataPointClick({ + x: data.categoryLabel, + y: data.dataValue, + }); + }, + }, ...this.getCustomFusionChartDataSource(), }; this.chartInstance = new FusionCharts(chartConfig); diff --git a/app/client/src/components/designSystems/appsmith/ContainerComponent.tsx b/app/client/src/components/designSystems/appsmith/ContainerComponent.tsx index 16cc613961..edda15651f 100644 --- a/app/client/src/components/designSystems/appsmith/ContainerComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/ContainerComponent.tsx @@ -1,9 +1,11 @@ import React, { ReactNode, useRef, useEffect, RefObject } from "react"; import styled, { css } from "styled-components"; +import tinycolor from "tinycolor2"; import { ComponentProps } from "./BaseComponent"; -import { getBorderCSSShorthand, invisible } from "constants/DefaultTheme"; +import { invisible } from "constants/DefaultTheme"; import { Color } from "constants/Colors"; import { generateClassName, getCanvasClassName } from "utils/generators"; +import { useCanvasMinHeightUpdateHook } from "utils/hooks/useCanvasMinHeightUpdateHook"; const scrollContents = css` overflow-y: auto; @@ -17,24 +19,44 @@ const StyledContainerComponent = styled.div< ${(props) => props.containerStyle !== "none" ? ` - border: ${getBorderCSSShorthand(props.theme.borders[2])}; + box-shadow: 0px 0px 0px 1px #E7E7E7; border-radius: 0;` : ""} height: 100%; width: 100%; background: ${(props) => props.backgroundColor}; - + opacity: ${(props) => (props.resizeDisabled ? "0.8" : "1")}; + position: relative; ${(props) => (!props.isVisible ? invisible : "")}; - opacity: ${(props) => (props.resizeDisabled ? "0.5" : "1")}; - pointer-events: ${(props) => (props.resizeDisabled ? "none" : "inherit")}; - overflow: hidden; - ${(props) => (props.shouldScrollContents ? scrollContents : "")} + box-shadow: ${(props) => + props.selected ? "0px 0px 0px 3px rgba(59,130,246,0.5)" : "none"}; + z-index: ${(props) => (props.focused ? "3" : props.selected ? "2" : "1")}; + ${(props) => + props.shouldScrollContents === true + ? scrollContents + : props.shouldScrollContents === false + ? css` + overflow: hidden; + ` + : ""} + + &:hover { + z-index: ${(props) => (props.onClickCapture ? "2" : "1")}; + cursor: ${(props) => (props.onClickCapture ? "pointer" : "inherit")}; + background: ${(props) => { + return props.onClickCapture && props.backgroundColor + ? tinycolor(props.backgroundColor) + .darken(5) + .toString() + : props.backgroundColor; + }}; + } }`; function ContainerComponent(props: ContainerComponentProps) { const containerStyle = props.containerStyle || "card"; const containerRef: RefObject = useRef(null); - + useCanvasMinHeightUpdateHook(props.widgetId, props.minHeight); useEffect(() => { if (!props.shouldScrollContents) { const supportsNativeSmoothScroll = @@ -73,6 +95,9 @@ export interface ContainerComponentProps extends ComponentProps { backgroundColor?: Color; shouldScrollContents?: boolean; resizeDisabled?: boolean; + selected?: boolean; + focused?: boolean; + minHeight?: number; } export default ContainerComponent; diff --git a/app/client/src/components/designSystems/appsmith/CreatableDropdown.tsx b/app/client/src/components/designSystems/appsmith/CreatableDropdown.tsx index d6465d62a9..7220d1656c 100644 --- a/app/client/src/components/designSystems/appsmith/CreatableDropdown.tsx +++ b/app/client/src/components/designSystems/appsmith/CreatableDropdown.tsx @@ -77,14 +77,14 @@ const selectStyles = { class CreatableDropdown extends React.Component { render() { const { - placeholder, - options, - isLoading, - input, - noOptionsMessage, components, + input, inputValue, + isLoading, + noOptionsMessage, onInputChange, + options, + placeholder, } = this.props; const optionalProps: Partial = {}; if (noOptionsMessage) optionalProps.noOptionsMessage = noOptionsMessage; diff --git a/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx b/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx index ca1fb37219..967ef2a981 100644 --- a/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx @@ -81,16 +81,16 @@ export class DroppableComponent extends React.Component< render() { const { - renderComponent, deleteOption, - updateOption, - toggleVisibility, onEdit, + renderComponent, + toggleVisibility, + updateOption, } = this.props; return ( - {({ innerRef, droppableProps, placeholder }) => ( + {({ droppableProps, innerRef, placeholder }) => ( } {...droppableProps} @@ -103,7 +103,7 @@ export class DroppableComponent extends React.Component< index={index} key={item.id} > - {({ innerRef, draggableProps, dragHandleProps }) => ( + {({ draggableProps, dragHandleProps, innerRef }) => ( } {...draggableProps} diff --git a/app/client/src/components/designSystems/appsmith/Dropdown.tsx b/app/client/src/components/designSystems/appsmith/Dropdown.tsx index ecade3fff9..29359e7c23 100644 --- a/app/client/src/components/designSystems/appsmith/Dropdown.tsx +++ b/app/client/src/components/designSystems/appsmith/Dropdown.tsx @@ -4,6 +4,7 @@ import Select from "react-select"; import { WrappedFieldInputProps } from "redux-form"; import { theme } from "constants/DefaultTheme"; import { SelectComponentsConfig } from "react-select/src/components"; +import { LayersContext } from "../../../constants/Layers"; export type DropdownProps = { options: Array<{ @@ -50,13 +51,21 @@ const selectStyles = { padding: "5px", }), indicatorSeparator: () => ({}), + menu: (provided: any) => ({ ...provided, zIndex: 2 }), + menuPortal: (base: any) => ({ ...base, zIndex: 2 }), }; export function BaseDropdown(props: DropdownProps) { - const { input, customSelectStyles } = props; + const layer = React.useContext(LayersContext); + const { customSelectStyles, input } = props; + const menuPortalStyle = { + menuPortal: (styles: any) => ({ ...styles, zIndex: layer.max }), + }; + return (