@@ -9,6 +9,6 @@ trim_trailing_whitespace = true | |||||
charset = utf-8 | charset = utf-8 | ||||
# python, js indentation settings | # python, js indentation settings | ||||
[{*.py,*.js}] | |||||
[{*.py,*.js,*.vue}] | |||||
indent_style = tab | indent_style = tab | ||||
indent_size = 4 | indent_size = 4 |
@@ -10,3 +10,6 @@ | |||||
# Replace use of Class.extend with native JS class | # Replace use of Class.extend with native JS class | ||||
fe20515c23a3ac41f1092bf0eaf0a0a452ec2e85 | fe20515c23a3ac41f1092bf0eaf0a0a452ec2e85 | ||||
# Updating license headers | |||||
34460265554242a8d05fb09f049033b1117e1a2b |
@@ -32,9 +32,9 @@ if __name__ == "__main__": | |||||
if response.ok: | if response.ok: | ||||
payload = response.json() | payload = response.json() | ||||
title = payload.get("title", "").lower() | |||||
head_sha = payload.get("head", {}).get("sha") | |||||
body = payload.get("body", "").lower() | |||||
title = (payload.get("title") or "").lower() | |||||
head_sha = (payload.get("head") or {}).get("sha") | |||||
body = (payload.get("body") or "").lower() | |||||
if title.startswith("feat") and head_sha and "no-docs" not in body: | if title.startswith("feat") and head_sha and "no-docs" not in body: | ||||
if docs_link_exists(body): | if docs_link_exists(body): | ||||
@@ -2,11 +2,6 @@ | |||||
set -e | set -e | ||||
# python "${GITHUB_WORKSPACE}/.github/helper/roulette.py" | |||||
# if [[ $? != 2 ]];then | |||||
# exit; | |||||
# fi | |||||
# install wkhtmltopdf | # install wkhtmltopdf | ||||
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz | wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz | ||||
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp | tar -xf /tmp/wkhtmltox.tar.xz -C /tmp | ||||
@@ -1,56 +1,72 @@ | |||||
# if the script ends with exit code 0, then no tests are run further, else all tests are run | |||||
import json | |||||
import os | import os | ||||
import re | import re | ||||
import shlex | import shlex | ||||
import subprocess | import subprocess | ||||
import sys | import sys | ||||
import urllib.request | |||||
def get_files_list(pr_number, repo="frappe/frappe"): | |||||
req = urllib.request.Request(f"https://api.github.com/repos/{repo}/pulls/{pr_number}/files") | |||||
res = urllib.request.urlopen(req) | |||||
dump = json.loads(res.read().decode('utf8')) | |||||
return [change["filename"] for change in dump] | |||||
def get_output(command, shell=True): | def get_output(command, shell=True): | ||||
print(command) | |||||
command = shlex.split(command) | |||||
return subprocess.check_output(command, shell=shell, encoding="utf8").strip() | |||||
print(command) | |||||
command = shlex.split(command) | |||||
return subprocess.check_output(command, shell=shell, encoding="utf8").strip() | |||||
def is_py(file): | def is_py(file): | ||||
return file.endswith("py") | |||||
return file.endswith("py") | |||||
def is_ci(file): | |||||
return ".github" in file | |||||
def is_js(file): | |||||
return file.endswith("js") | |||||
def is_frontend_code(file): | |||||
return file.lower().endswith((".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue")) | |||||
def is_docs(file): | def is_docs(file): | ||||
regex = re.compile(r'\.(md|png|jpg|jpeg)$|^.github|LICENSE') | |||||
return bool(regex.search(file)) | |||||
regex = re.compile(r'\.(md|png|jpg|jpeg|csv)$|^.github|LICENSE') | |||||
return bool(regex.search(file)) | |||||
if __name__ == "__main__": | if __name__ == "__main__": | ||||
build_type = os.environ.get("TYPE") | |||||
before = os.environ.get("BEFORE") | |||||
after = os.environ.get("AFTER") | |||||
commit_range = before + '...' + after | |||||
print("Build Type: {}".format(build_type)) | |||||
print("Commit Range: {}".format(commit_range)) | |||||
try: | |||||
files_changed = get_output("git diff --name-only {}".format(commit_range), shell=False) | |||||
except Exception: | |||||
sys.exit(2) | |||||
if "fatal" not in files_changed: | |||||
files_list = files_changed.split() | |||||
only_docs_changed = len(list(filter(is_docs, files_list))) == len(files_list) | |||||
only_js_changed = len(list(filter(is_js, files_list))) == len(files_list) | |||||
only_py_changed = len(list(filter(is_py, files_list))) == len(files_list) | |||||
if only_docs_changed: | |||||
print("Only docs were updated, stopping build process.") | |||||
sys.exit(0) | |||||
if only_js_changed and build_type == "server": | |||||
print("Only JavaScript code was updated; Stopping Python build process.") | |||||
sys.exit(0) | |||||
if only_py_changed and build_type == "ui": | |||||
print("Only Python code was updated, stopping Cypress build process.") | |||||
sys.exit(0) | |||||
sys.exit(2) | |||||
files_list = sys.argv[1:] | |||||
build_type = os.environ.get("TYPE") | |||||
pr_number = os.environ.get("PR_NUMBER") | |||||
repo = os.environ.get("REPO_NAME") | |||||
# this is a push build, run all builds | |||||
if not pr_number: | |||||
os.system('echo "::set-output name=build::strawberry"') | |||||
sys.exit(0) | |||||
files_list = files_list or get_files_list(pr_number=pr_number, repo=repo) | |||||
if not files_list: | |||||
print("No files' changes detected. Build is shutting") | |||||
sys.exit(0) | |||||
ci_files_changed = any(f for f in files_list if is_ci(f)) | |||||
only_docs_changed = len(list(filter(is_docs, files_list))) == len(files_list) | |||||
only_frontend_code_changed = len(list(filter(is_frontend_code, files_list))) == len(files_list) | |||||
only_py_changed = len(list(filter(is_py, files_list))) == len(files_list) | |||||
if ci_files_changed: | |||||
print("CI related files were updated, running all build processes.") | |||||
elif only_docs_changed: | |||||
print("Only docs were updated, stopping build process.") | |||||
sys.exit(0) | |||||
elif only_frontend_code_changed and build_type == "server": | |||||
print("Only Frontend code was updated; Stopping Python build process.") | |||||
sys.exit(0) | |||||
elif only_py_changed and build_type == "ui": | |||||
print("Only Python code was updated, stopping Cypress build process.") | |||||
sys.exit(0) | |||||
os.system('echo "::set-output name=build::strawberry"') |
@@ -98,8 +98,6 @@ rules: | |||||
languages: [python] | languages: [python] | ||||
severity: WARNING | severity: WARNING | ||||
paths: | paths: | ||||
exclude: | |||||
- test_*.py | |||||
include: | include: | ||||
- "*/**/doctype/*" | - "*/**/doctype/*" | ||||
@@ -8,10 +8,6 @@ rules: | |||||
dynamic content. Avoid it or use safe_eval(). | dynamic content. Avoid it or use safe_eval(). | ||||
languages: [python] | languages: [python] | ||||
severity: ERROR | severity: ERROR | ||||
paths: | |||||
exclude: | |||||
- frappe/__init__.py | |||||
- frappe/commands/utils.py | |||||
- id: frappe-sqli-format-strings | - id: frappe-sqli-format-strings | ||||
patterns: | patterns: | ||||
@@ -11,3 +11,20 @@ allowRevertCommits: true | |||||
# For allowed PR types: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json | # For allowed PR types: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json | ||||
# Tool Reference: https://github.com/zeke/semantic-pull-requests | # Tool Reference: https://github.com/zeke/semantic-pull-requests | ||||
# By default types specified in commitizen/conventional-commit-types is used. | |||||
# See: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json | |||||
# You can override the valid types | |||||
types: | |||||
- BREAKING CHANGE | |||||
- feat | |||||
- fix | |||||
- docs | |||||
- style | |||||
- refactor | |||||
- perf | |||||
- test | |||||
- build | |||||
- ci | |||||
- chore | |||||
- revert |
@@ -2,6 +2,11 @@ name: Patch | |||||
on: [pull_request, workflow_dispatch] | on: [pull_request, workflow_dispatch] | ||||
concurrency: | |||||
group: patch-mariadb-develop-${{ github.event.number }} | |||||
cancel-in-progress: true | |||||
jobs: | jobs: | ||||
test: | test: | ||||
runs-on: ubuntu-18.04 | runs-on: ubuntu-18.04 | ||||
@@ -26,10 +31,21 @@ jobs: | |||||
with: | with: | ||||
python-version: 3.7 | python-version: 3.7 | ||||
- name: Check if build should be run | |||||
id: check-build | |||||
run: | | |||||
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py" | |||||
env: | |||||
TYPE: "server" | |||||
PR_NUMBER: ${{ github.event.number }} | |||||
REPO_NAME: ${{ github.repository }} | |||||
- name: Add to Hosts | - name: Add to Hosts | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts | run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts | ||||
- name: Cache pip | - name: Cache pip | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: actions/cache@v2 | uses: actions/cache@v2 | ||||
with: | with: | ||||
path: ~/.cache/pip | path: ~/.cache/pip | ||||
@@ -39,6 +55,7 @@ jobs: | |||||
${{ runner.os }}- | ${{ runner.os }}- | ||||
- name: Cache node modules | - name: Cache node modules | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: actions/cache@v2 | uses: actions/cache@v2 | ||||
env: | env: | ||||
cache-name: cache-node-modules | cache-name: cache-node-modules | ||||
@@ -51,10 +68,12 @@ jobs: | |||||
${{ runner.os }}- | ${{ runner.os }}- | ||||
- name: Get yarn cache directory path | - name: Get yarn cache directory path | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
id: yarn-cache-dir-path | id: yarn-cache-dir-path | ||||
run: echo "::set-output name=dir::$(yarn cache dir)" | run: echo "::set-output name=dir::$(yarn cache dir)" | ||||
- uses: actions/cache@v2 | - uses: actions/cache@v2 | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
id: yarn-cache | id: yarn-cache | ||||
with: | with: | ||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | ||||
@@ -63,6 +82,7 @@ jobs: | |||||
${{ runner.os }}-yarn- | ${{ runner.os }}-yarn- | ||||
- name: Install Dependencies | - name: Install Dependencies | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh | run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh | ||||
env: | env: | ||||
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} | BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} | ||||
@@ -70,12 +90,14 @@ jobs: | |||||
TYPE: server | TYPE: server | ||||
- name: Install | - name: Install | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh | run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh | ||||
env: | env: | ||||
DB: mariadb | DB: mariadb | ||||
TYPE: server | TYPE: server | ||||
- name: Run Patch Tests | - name: Run Patch Tests | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: | | run: | | ||||
cd ~/frappe-bench/ | cd ~/frappe-bench/ | ||||
wget https://frappeframework.com/files/v10-frappe.sql.gz | wget https://frappeframework.com/files/v10-frappe.sql.gz | ||||
@@ -1,34 +1,18 @@ | |||||
name: Semgrep | name: Semgrep | ||||
on: | on: | ||||
pull_request: | |||||
branches: | |||||
- develop | |||||
- version-13-hotfix | |||||
- version-13-pre-release | |||||
pull_request: { } | |||||
jobs: | jobs: | ||||
semgrep: | semgrep: | ||||
name: Frappe Linter | name: Frappe Linter | ||||
runs-on: ubuntu-latest | runs-on: ubuntu-latest | ||||
steps: | steps: | ||||
- uses: actions/checkout@v2 | |||||
- name: Setup python3 | |||||
uses: actions/setup-python@v2 | |||||
with: | |||||
python-version: 3.8 | |||||
- name: Setup semgrep | |||||
run: | | |||||
python -m pip install -q semgrep | |||||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q | |||||
- name: Semgrep errors | |||||
run: | | |||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) | |||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files | |||||
semgrep --config="r/python.lang.correctness" --quiet --error $files | |||||
- name: Semgrep warnings | |||||
run: | | |||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) | |||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files | |||||
- uses: actions/checkout@v2 | |||||
- uses: returntocorp/semgrep-action@v1 | |||||
env: | |||||
SEMGREP_TIMEOUT: 120 | |||||
with: | |||||
config: >- | |||||
r/python.lang.correctness | |||||
.github/helper/semgrep_rules |
@@ -6,6 +6,11 @@ on: | |||||
push: | push: | ||||
branches: [ develop ] | branches: [ develop ] | ||||
concurrency: | |||||
group: server-mariadb-develop-${{ github.event.number }} | |||||
cancel-in-progress: true | |||||
jobs: | jobs: | ||||
test: | test: | ||||
runs-on: ubuntu-18.04 | runs-on: ubuntu-18.04 | ||||
@@ -35,17 +40,29 @@ jobs: | |||||
with: | with: | ||||
python-version: 3.7 | python-version: 3.7 | ||||
- name: Check if build should be run | |||||
id: check-build | |||||
run: | | |||||
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py" | |||||
env: | |||||
TYPE: "server" | |||||
PR_NUMBER: ${{ github.event.number }} | |||||
REPO_NAME: ${{ github.repository }} | |||||
- uses: actions/setup-node@v2 | - uses: actions/setup-node@v2 | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
with: | with: | ||||
node-version: 14 | node-version: 14 | ||||
check-latest: true | check-latest: true | ||||
- name: Add to Hosts | - name: Add to Hosts | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: | | run: | | ||||
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts | echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts | ||||
echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts | echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts | ||||
- name: Cache pip | - name: Cache pip | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: actions/cache@v2 | uses: actions/cache@v2 | ||||
with: | with: | ||||
path: ~/.cache/pip | path: ~/.cache/pip | ||||
@@ -55,6 +72,7 @@ jobs: | |||||
${{ runner.os }}- | ${{ runner.os }}- | ||||
- name: Cache node modules | - name: Cache node modules | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: actions/cache@v2 | uses: actions/cache@v2 | ||||
env: | env: | ||||
cache-name: cache-node-modules | cache-name: cache-node-modules | ||||
@@ -67,10 +85,12 @@ jobs: | |||||
${{ runner.os }}- | ${{ runner.os }}- | ||||
- name: Get yarn cache directory path | - name: Get yarn cache directory path | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
id: yarn-cache-dir-path | id: yarn-cache-dir-path | ||||
run: echo "::set-output name=dir::$(yarn cache dir)" | run: echo "::set-output name=dir::$(yarn cache dir)" | ||||
- uses: actions/cache@v2 | - uses: actions/cache@v2 | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
id: yarn-cache | id: yarn-cache | ||||
with: | with: | ||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | ||||
@@ -79,6 +99,7 @@ jobs: | |||||
${{ runner.os }}-yarn- | ${{ runner.os }}-yarn- | ||||
- name: Install Dependencies | - name: Install Dependencies | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh | run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh | ||||
env: | env: | ||||
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} | BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} | ||||
@@ -86,45 +107,24 @@ jobs: | |||||
TYPE: server | TYPE: server | ||||
- name: Install | - name: Install | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh | run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh | ||||
env: | env: | ||||
DB: mariadb | DB: mariadb | ||||
TYPE: server | TYPE: server | ||||
- name: Run Tests | - name: Run Tests | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator --with-coverage | run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator --with-coverage | ||||
env: | env: | ||||
CI_BUILD_ID: ${{ github.run_id }} | CI_BUILD_ID: ${{ github.run_id }} | ||||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io | ORCHESTRATOR_URL: http://test-orchestrator.frappe.io | ||||
- name: Upload Coverage Data | |||||
run: | | |||||
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} | |||||
cd ${GITHUB_WORKSPACE} | |||||
pip3 install coverage==5.5 | |||||
pip3 install coveralls==3.0.1 | |||||
coveralls | |||||
env: | |||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} | |||||
COVERALLS_FLAG_NAME: run-${{ matrix.container }} | |||||
COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} | |||||
COVERALLS_PARALLEL: true | |||||
coveralls: | |||||
name: Coverage Wrap Up | |||||
needs: test | |||||
container: python:3-slim | |||||
runs-on: ubuntu-18.04 | |||||
steps: | |||||
- name: Clone | |||||
uses: actions/checkout@v2 | |||||
- name: Coveralls Finished | |||||
run: | | |||||
cd ${GITHUB_WORKSPACE} | |||||
pip3 install coverage==5.5 | |||||
pip3 install coveralls==3.0.1 | |||||
coveralls --finish | |||||
env: | |||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |||||
- name: Upload coverage data | |||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: codecov/codecov-action@v2 | |||||
with: | |||||
name: MariaDB | |||||
fail_ci_if_error: true | |||||
files: /home/runner/frappe-bench/sites/coverage.xml | |||||
verbose: true |
@@ -3,6 +3,12 @@ name: Server | |||||
on: | on: | ||||
pull_request: | pull_request: | ||||
workflow_dispatch: | workflow_dispatch: | ||||
push: | |||||
branches: [ develop ] | |||||
concurrency: | |||||
group: server-postgres-develop-${{ github.event.number }} | |||||
cancel-in-progress: true | |||||
jobs: | jobs: | ||||
test: | test: | ||||
@@ -37,17 +43,29 @@ jobs: | |||||
with: | with: | ||||
python-version: 3.7 | python-version: 3.7 | ||||
- name: Check if build should be run | |||||
id: check-build | |||||
run: | | |||||
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py" | |||||
env: | |||||
TYPE: "server" | |||||
PR_NUMBER: ${{ github.event.number }} | |||||
REPO_NAME: ${{ github.repository }} | |||||
- uses: actions/setup-node@v2 | - uses: actions/setup-node@v2 | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
with: | with: | ||||
node-version: '14' | node-version: '14' | ||||
check-latest: true | check-latest: true | ||||
- name: Add to Hosts | - name: Add to Hosts | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: | | run: | | ||||
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts | echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts | ||||
echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts | echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts | ||||
- name: Cache pip | - name: Cache pip | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: actions/cache@v2 | uses: actions/cache@v2 | ||||
with: | with: | ||||
path: ~/.cache/pip | path: ~/.cache/pip | ||||
@@ -57,6 +75,7 @@ jobs: | |||||
${{ runner.os }}- | ${{ runner.os }}- | ||||
- name: Cache node modules | - name: Cache node modules | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: actions/cache@v2 | uses: actions/cache@v2 | ||||
env: | env: | ||||
cache-name: cache-node-modules | cache-name: cache-node-modules | ||||
@@ -69,10 +88,12 @@ jobs: | |||||
${{ runner.os }}- | ${{ runner.os }}- | ||||
- name: Get yarn cache directory path | - name: Get yarn cache directory path | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
id: yarn-cache-dir-path | id: yarn-cache-dir-path | ||||
run: echo "::set-output name=dir::$(yarn cache dir)" | run: echo "::set-output name=dir::$(yarn cache dir)" | ||||
- uses: actions/cache@v2 | - uses: actions/cache@v2 | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
id: yarn-cache | id: yarn-cache | ||||
with: | with: | ||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | ||||
@@ -81,6 +102,7 @@ jobs: | |||||
${{ runner.os }}-yarn- | ${{ runner.os }}-yarn- | ||||
- name: Install Dependencies | - name: Install Dependencies | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh | run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh | ||||
env: | env: | ||||
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} | BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} | ||||
@@ -88,13 +110,24 @@ jobs: | |||||
TYPE: server | TYPE: server | ||||
- name: Install | - name: Install | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh | run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh | ||||
env: | env: | ||||
DB: postgres | DB: postgres | ||||
TYPE: server | TYPE: server | ||||
- name: Run Tests | - name: Run Tests | ||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator | |||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator --with-coverage | |||||
env: | env: | ||||
CI_BUILD_ID: ${{ github.run_id }} | CI_BUILD_ID: ${{ github.run_id }} | ||||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io | ORCHESTRATOR_URL: http://test-orchestrator.frappe.io | ||||
- name: Upload coverage data | |||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: codecov/codecov-action@v2 | |||||
with: | |||||
name: Postgres | |||||
fail_ci_if_error: true | |||||
files: /home/runner/frappe-bench/sites/coverage.xml | |||||
verbose: true |
@@ -6,6 +6,10 @@ on: | |||||
push: | push: | ||||
branches: [ develop ] | branches: [ develop ] | ||||
concurrency: | |||||
group: ui-develop-${{ github.event.number }} | |||||
cancel-in-progress: true | |||||
jobs: | jobs: | ||||
test: | test: | ||||
runs-on: ubuntu-18.04 | runs-on: ubuntu-18.04 | ||||
@@ -35,17 +39,29 @@ jobs: | |||||
with: | with: | ||||
python-version: 3.7 | python-version: 3.7 | ||||
- name: Check if build should be run | |||||
id: check-build | |||||
run: | | |||||
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py" | |||||
env: | |||||
TYPE: "ui" | |||||
PR_NUMBER: ${{ github.event.number }} | |||||
REPO_NAME: ${{ github.repository }} | |||||
- uses: actions/setup-node@v2 | - uses: actions/setup-node@v2 | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
with: | with: | ||||
node-version: 14 | node-version: 14 | ||||
check-latest: true | check-latest: true | ||||
- name: Add to Hosts | - name: Add to Hosts | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: | | run: | | ||||
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts | echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts | ||||
echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts | echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts | ||||
- name: Cache pip | - name: Cache pip | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: actions/cache@v2 | uses: actions/cache@v2 | ||||
with: | with: | ||||
path: ~/.cache/pip | path: ~/.cache/pip | ||||
@@ -55,6 +71,7 @@ jobs: | |||||
${{ runner.os }}- | ${{ runner.os }}- | ||||
- name: Cache node modules | - name: Cache node modules | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: actions/cache@v2 | uses: actions/cache@v2 | ||||
env: | env: | ||||
cache-name: cache-node-modules | cache-name: cache-node-modules | ||||
@@ -67,10 +84,12 @@ jobs: | |||||
${{ runner.os }}- | ${{ runner.os }}- | ||||
- name: Get yarn cache directory path | - name: Get yarn cache directory path | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
id: yarn-cache-dir-path | id: yarn-cache-dir-path | ||||
run: echo "::set-output name=dir::$(yarn cache dir)" | run: echo "::set-output name=dir::$(yarn cache dir)" | ||||
- uses: actions/cache@v2 | - uses: actions/cache@v2 | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
id: yarn-cache | id: yarn-cache | ||||
with: | with: | ||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} | ||||
@@ -79,6 +98,7 @@ jobs: | |||||
${{ runner.os }}-yarn- | ${{ runner.os }}-yarn- | ||||
- name: Cache cypress binary | - name: Cache cypress binary | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
uses: actions/cache@v2 | uses: actions/cache@v2 | ||||
with: | with: | ||||
path: ~/.cache | path: ~/.cache | ||||
@@ -88,6 +108,7 @@ jobs: | |||||
${{ runner.os }}- | ${{ runner.os }}- | ||||
- name: Install Dependencies | - name: Install Dependencies | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh | run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh | ||||
env: | env: | ||||
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} | BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} | ||||
@@ -95,13 +116,18 @@ jobs: | |||||
TYPE: ui | TYPE: ui | ||||
- name: Install | - name: Install | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh | run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh | ||||
env: | env: | ||||
DB: mariadb | DB: mariadb | ||||
TYPE: ui | TYPE: ui | ||||
- name: Site Setup | - name: Site Setup | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: cd ~/frappe-bench/ && bench --site test_site execute frappe.utils.install.complete_setup_wizard | run: cd ~/frappe-bench/ && bench --site test_site execute frappe.utils.install.complete_setup_wizard | ||||
- name: UI Tests | - name: UI Tests | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||||
run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests frappe --headless --parallel --ci-build-id $GITHUB_RUN_ID | run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests frappe --headless --parallel --ci-build-id $GITHUB_RUN_ID | ||||
env: | |||||
CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb |
@@ -1,4 +1,20 @@ | |||||
pull_request_rules: | pull_request_rules: | ||||
- name: Auto-close PRs on stable branch | |||||
conditions: | |||||
- and: | |||||
- and: | |||||
- author!=surajshetty3416 | |||||
- author!=gavindsouza | |||||
- or: | |||||
- base=version-13 | |||||
- base=version-12 | |||||
actions: | |||||
close: | |||||
comment: | |||||
message: | | |||||
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch. | |||||
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch | |||||
- name: Automatic merge on CI success and review | - name: Automatic merge on CI success and review | ||||
conditions: | conditions: | ||||
- status-success=Sider | - status-success=Sider | ||||
@@ -4,16 +4,16 @@ | |||||
# the repo. Unless a later match takes precedence, | # the repo. Unless a later match takes precedence, | ||||
* @frappe/frappe-review-team | * @frappe/frappe-review-team | ||||
website/ @prssanna | |||||
web_form/ @prssanna | |||||
templates/ @surajshetty3416 | templates/ @surajshetty3416 | ||||
www/ @surajshetty3416 | www/ @surajshetty3416 | ||||
integrations/ @leela | integrations/ @leela | ||||
patches/ @surajshetty3416 | |||||
dashboard/ @prssanna | |||||
patches/ @surajshetty3416 @gavindsouza | |||||
email/ @leela | email/ @leela | ||||
event_streaming/ @ruchamahabal | event_streaming/ @ruchamahabal | ||||
data_import* @netchampfaris | data_import* @netchampfaris | ||||
core/ @surajshetty3416 | core/ @surajshetty3416 | ||||
database @gavindsouza | |||||
model @gavindsouza | |||||
requirements.txt @gavindsouza | requirements.txt @gavindsouza | ||||
commands/ @gavindsouza | commands/ @gavindsouza | ||||
workspace @shariquerik |
@@ -1,6 +1,6 @@ | |||||
The MIT License | The MIT License | ||||
Copyright (c) 2016-2018 Frappe Technologies Pvt. Ltd. <developers@frappe.io> | |||||
Copyright (c) 2016-2021 Frappe Technologies Pvt. Ltd. <developers@frappe.io> | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||
@@ -26,8 +26,8 @@ | |||||
<a href='https://www.codetriage.com/frappe/frappe'> | <a href='https://www.codetriage.com/frappe/frappe'> | ||||
<img src='https://www.codetriage.com/frappe/frappe/badges/users.svg'> | <img src='https://www.codetriage.com/frappe/frappe/badges/users.svg'> | ||||
</a> | </a> | ||||
<a href='https://coveralls.io/github/frappe/frappe?branch=develop'> | |||||
<img src='https://coveralls.io/repos/github/frappe/frappe/badge.svg?branch=develop'> | |||||
<a href="https://codecov.io/gh/frappe/frappe"> | |||||
<img src="https://codecov.io/gh/frappe/frappe/branch/develop/graph/badge.svg?token=XoTa679hIj"/> | |||||
</a> | </a> | ||||
</div> | </div> | ||||
@@ -0,0 +1,13 @@ | |||||
codecov: | |||||
require_ci_to_pass: yes | |||||
coverage: | |||||
status: | |||||
project: | |||||
default: | |||||
target: auto | |||||
threshold: 0.5% | |||||
comment: | |||||
layout: "diff" | |||||
require_changes: true |
@@ -31,8 +31,13 @@ context('API Resources', () => { | |||||
}); | }); | ||||
it('Removes the Comments', () => { | it('Removes the Comments', () => { | ||||
cy.get_list('Comment').then(body => body.data.forEach(comment => { | |||||
cy.remove_doc('Comment', comment.name); | |||||
})); | |||||
cy.get_list('Comment').then(body => { | |||||
let comment_names = []; | |||||
body.data.map(comment => comment_names.push(comment.name)); | |||||
comment_names = [...new Set(comment_names)]; // remove duplicates | |||||
comment_names.forEach((comment_name) => { | |||||
cy.remove_doc('Comment', comment_name); | |||||
}); | |||||
}); | |||||
}); | }); | ||||
}); | }); |
@@ -10,9 +10,9 @@ context('Awesome Bar', () => { | |||||
}); | }); | ||||
it('navigates to doctype list', () => { | it('navigates to doctype list', () => { | ||||
cy.get('#navbar-search').type('todo', { delay: 200 }); | |||||
cy.get('#navbar-search + ul').should('be.visible'); | |||||
cy.get('#navbar-search').type('{downarrow}{enter}', { delay: 100 }); | |||||
cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('todo', { delay: 200 }); | |||||
cy.get('.awesomplete').findByRole('listbox').should('be.visible'); | |||||
cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('{downarrow}{enter}', { delay: 100 }); | |||||
cy.get('.title-text').should('contain', 'To Do'); | cy.get('.title-text').should('contain', 'To Do'); | ||||
@@ -20,24 +20,24 @@ context('Awesome Bar', () => { | |||||
}); | }); | ||||
it('find text in doctype list', () => { | it('find text in doctype list', () => { | ||||
cy.get('#navbar-search') | |||||
cy.findByPlaceholderText('Search or type a command (Ctrl + G)') | |||||
.type('test in todo{downarrow}{enter}', { delay: 200 }); | .type('test in todo{downarrow}{enter}', { delay: 200 }); | ||||
cy.get('.title-text').should('contain', 'To Do'); | cy.get('.title-text').should('contain', 'To Do'); | ||||
cy.get('[data-original-title="Name"] > .input-with-feedback') | |||||
cy.findByPlaceholderText('Name') | |||||
.should('have.value', '%test%'); | .should('have.value', '%test%'); | ||||
}); | }); | ||||
it('navigates to new form', () => { | it('navigates to new form', () => { | ||||
cy.get('#navbar-search') | |||||
cy.findByPlaceholderText('Search or type a command (Ctrl + G)') | |||||
.type('new blog post{downarrow}{enter}', { delay: 200 }); | .type('new blog post{downarrow}{enter}', { delay: 200 }); | ||||
cy.get('.title-text:visible').should('have.text', 'New Blog Post'); | cy.get('.title-text:visible').should('have.text', 'New Blog Post'); | ||||
}); | }); | ||||
it('calculates math expressions', () => { | it('calculates math expressions', () => { | ||||
cy.get('#navbar-search') | |||||
cy.findByPlaceholderText('Search or type a command (Ctrl + G)') | |||||
.type('55 + 32{downarrow}{enter}', { delay: 200 }); | .type('55 + 32{downarrow}{enter}', { delay: 200 }); | ||||
cy.get('.modal-title').should('contain', 'Result'); | cy.get('.modal-title').should('contain', 'Result'); | ||||
@@ -20,7 +20,7 @@ context('Control Barcode', () => { | |||||
it('should generate barcode on setting a value', () => { | it('should generate barcode on setting a value', () => { | ||||
get_dialog_with_barcode().as('dialog'); | get_dialog_with_barcode().as('dialog'); | ||||
cy.get('.frappe-control[data-fieldname=barcode] input') | |||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') | |||||
.focus() | .focus() | ||||
.type('123456789') | .type('123456789') | ||||
.blur(); | .blur(); | ||||
@@ -37,11 +37,11 @@ context('Control Barcode', () => { | |||||
it('should reset when input is cleared', () => { | it('should reset when input is cleared', () => { | ||||
get_dialog_with_barcode().as('dialog'); | get_dialog_with_barcode().as('dialog'); | ||||
cy.get('.frappe-control[data-fieldname=barcode] input') | |||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') | |||||
.focus() | .focus() | ||||
.type('123456789') | .type('123456789') | ||||
.blur(); | .blur(); | ||||
cy.get('.frappe-control[data-fieldname=barcode] input') | |||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') | |||||
.clear() | .clear() | ||||
.blur(); | .blur(); | ||||
cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]') | cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]') | ||||
@@ -0,0 +1,50 @@ | |||||
context('Control Icon', () => { | |||||
before(() => { | |||||
cy.login(); | |||||
cy.visit('/app/website'); | |||||
}); | |||||
function get_dialog_with_icon() { | |||||
return cy.dialog({ | |||||
title: 'Icon', | |||||
fields: [{ | |||||
label: 'Icon', | |||||
fieldname: 'icon', | |||||
fieldtype: 'Icon' | |||||
}] | |||||
}); | |||||
} | |||||
it('should set icon', () => { | |||||
get_dialog_with_icon().as('dialog'); | |||||
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').click(); | |||||
cy.get('.icon-picker .icon-wrapper[id=active]').first().click(); | |||||
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'active'); | |||||
cy.get('@dialog').then(dialog => { | |||||
let value = dialog.get_value('icon'); | |||||
expect(value).to.equal('active'); | |||||
}); | |||||
cy.get('.icon-picker .icon-wrapper[id=resting]').first().click(); | |||||
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'resting'); | |||||
cy.get('@dialog').then(dialog => { | |||||
let value = dialog.get_value('icon'); | |||||
expect(value).to.equal('resting'); | |||||
}); | |||||
}); | |||||
it('search for icon and clear search input', () => { | |||||
let search_text = 'ed'; | |||||
cy.get('.icon-picker').findByRole('searchbox').click().type(search_text); | |||||
cy.get('.icon-section .icon-wrapper:not(.hidden)').then(i => { | |||||
cy.get(`.icon-section .icon-wrapper[id*='${search_text}']`).then(icons => { | |||||
expect(i.length).to.equal(icons.length); | |||||
}); | |||||
}); | |||||
cy.get('.icon-picker').findByRole('searchbox').clear().blur(); | |||||
cy.get('.icon-section .icon-wrapper').should('not.have.class', 'hidden'); | |||||
}); | |||||
}); |
@@ -35,7 +35,7 @@ context('Control Link', () => { | |||||
cy.wait('@search_link'); | cy.wait('@search_link'); | ||||
cy.get('@input').type('todo for link', { delay: 200 }); | cy.get('@input').type('todo for link', { delay: 200 }); | ||||
cy.wait('@search_link'); | cy.wait('@search_link'); | ||||
cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible'); | |||||
cy.get('.frappe-control[data-fieldname=link]').findByRole('listbox').should('be.visible'); | |||||
cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 }); | cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 }); | ||||
cy.get('.frappe-control[data-fieldname=link] input').blur(); | cy.get('.frappe-control[data-fieldname=link] input').blur(); | ||||
cy.get('@dialog').then(dialog => { | cy.get('@dialog').then(dialog => { | ||||
@@ -71,7 +71,7 @@ context('Control Link', () => { | |||||
cy.get('@input').type(todos[0]).blur(); | cy.get('@input').type(todos[0]).blur(); | ||||
cy.wait('@validate_link'); | cy.wait('@validate_link'); | ||||
cy.get('@input').focus(); | cy.get('@input').focus(); | ||||
cy.get('.frappe-control[data-fieldname=link] .link-btn') | |||||
cy.findByTitle('Open Link') | |||||
.should('be.visible') | .should('be.visible') | ||||
.click(); | .click(); | ||||
cy.location('pathname').should('eq', `/app/todo/${todos[0]}`); | cy.location('pathname').should('eq', `/app/todo/${todos[0]}`); | ||||
@@ -24,8 +24,10 @@ context('Control Select', () => { | |||||
cy.get('@control').get('.select-icon').should('exist'); | cy.get('@control').get('.select-icon').should('exist'); | ||||
cy.get('@control').get('.placeholder').should('have.css', 'display', 'block'); | cy.get('@control').get('.placeholder').should('have.css', 'display', 'block'); | ||||
cy.get('@select').select('Option 1'); | cy.get('@select').select('Option 1'); | ||||
cy.findByDisplayValue('Option 1').should('exist'); | |||||
cy.get('@control').get('.placeholder').should('have.css', 'display', 'none'); | cy.get('@control').get('.placeholder').should('have.css', 'display', 'none'); | ||||
cy.get('@select').invoke('val', ''); | cy.get('@select').invoke('val', ''); | ||||
cy.findByDisplayValue('Option 1').should('not.exist'); | |||||
cy.get('@control').get('.placeholder').should('have.css', 'display', 'block'); | cy.get('@control').get('.placeholder').should('have.css', 'display', 'block'); | ||||
@@ -0,0 +1,63 @@ | |||||
context('Dashboard links', () => { | |||||
before(() => { | |||||
cy.visit('/login'); | |||||
cy.login(); | |||||
}); | |||||
it('Adding a new contact, checking for the counter on the dashboard and deleting the created contact', () => { | |||||
cy.visit('/app/contact'); | |||||
cy.clear_filters(); | |||||
cy.visit('/app/user'); | |||||
cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click(); | |||||
//To check if initially the dashboard contains only the "Contact" link and there is no counter | |||||
cy.get('[data-doctype="Contact"]').should('contain', 'Contact'); | |||||
//Adding a new contact | |||||
cy.get('.btn[data-doctype="Contact"]').click(); | |||||
cy.get('[data-doctype="Contact"][data-fieldname="first_name"]').type('Admin'); | |||||
cy.findByRole('button', {name: 'Save'}).click(); | |||||
cy.visit('/app/user'); | |||||
cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click(); | |||||
//To check if the counter for contact doc is "1" after adding the contact | |||||
cy.get('[data-doctype="Contact"] > .count').should('contain', '1'); | |||||
cy.get('[data-doctype="Contact"]').contains('Contact').click(); | |||||
//Deleting the newly created contact | |||||
cy.visit('/app/contact'); | |||||
cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click(); | |||||
cy.findByRole('button', {name: 'Actions'}).click(); | |||||
cy.get('.actions-btn-group [data-label="Delete"]').click(); | |||||
cy.findByRole('button', {name: 'Yes'}).click({delay: 700}); | |||||
//To check if the counter from the "Contact" doc link is removed | |||||
cy.wait(700); | |||||
cy.visit('/app/user'); | |||||
cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click(); | |||||
cy.get('[data-doctype="Contact"]').should('contain', 'Contact'); | |||||
}); | |||||
it('Report link in dashboard', () => { | |||||
cy.visit('/app/user'); | |||||
cy.visit('/app/user/Administrator'); | |||||
cy.get('[data-doctype="Contact"]').should('contain', 'Contact'); | |||||
cy.findByText('Connections'); | |||||
cy.window() | |||||
.its('cur_frm') | |||||
.then(cur_frm => { | |||||
cur_frm.dashboard.data.reports = [ | |||||
{ | |||||
'label': 'Reports', | |||||
'items': ['Permitted Documents For User'] | |||||
} | |||||
]; | |||||
cur_frm.dashboard.render_report_links(); | |||||
cy.get('[data-report="Permitted Documents For User"]').contains('Permitted Documents For User').click(); | |||||
cy.findByText('Permitted Documents For User'); | |||||
cy.findByPlaceholderText('User').should("have.value", "Administrator"); | |||||
}); | |||||
}); | |||||
}); |
@@ -0,0 +1,19 @@ | |||||
// TODO: Enable this again | |||||
// currently this is flaky possibly because of different timezone in CI | |||||
// context('Datetime Field Validation', () => { | |||||
// before(() => { | |||||
// cy.login(); | |||||
// cy.visit('/app/communication'); | |||||
// }); | |||||
// it('datetime field form validation', () => { | |||||
// // validating datetime field value when value is set from backend and get validated on form load. | |||||
// cy.window().its('frappe').then(frappe => { | |||||
// return frappe.xcall("frappe.tests.ui_test_helpers.create_communication_record"); | |||||
// }).then(doc => { | |||||
// cy.visit(`/app/communication/${doc.name}`); | |||||
// cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red'); | |||||
// }); | |||||
// }); | |||||
// }); |
@@ -62,11 +62,11 @@ context('Depends On', () => { | |||||
it('should set the field as mandatory depending on other fields value', () => { | it('should set the field as mandatory depending on other fields value', () => { | ||||
cy.new_form('Test Depends On'); | cy.new_form('Test Depends On'); | ||||
cy.fill_field('test_field', 'Some Value'); | cy.fill_field('test_field', 'Some Value'); | ||||
cy.get('button.primary-action').contains('Save').click(); | |||||
cy.findByRole('button', {name: 'Save'}).click(); | |||||
cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('be.visible'); | cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('be.visible'); | ||||
cy.hide_dialog(); | cy.hide_dialog(); | ||||
cy.fill_field('test_field', 'Random value'); | cy.fill_field('test_field', 'Random value'); | ||||
cy.get('button.primary-action').contains('Save').click(); | |||||
cy.findByRole('button', {name: 'Save'}).click(); | |||||
cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('not.be.visible'); | cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('not.be.visible'); | ||||
}); | }); | ||||
it('should set the field as read only depending on other fields value', () => { | it('should set the field as read only depending on other fields value', () => { | ||||
@@ -84,7 +84,7 @@ context('Depends On', () => { | |||||
cy.fill_field('dependant_field', 'Some Value'); | cy.fill_field('dependant_field', 'Some Value'); | ||||
//cy.fill_field('test_field', 'Some Other Value'); | //cy.fill_field('test_field', 'Some Other Value'); | ||||
cy.get('.frappe-control[data-fieldname="child_test_depends_on_field"]').as('table'); | cy.get('.frappe-control[data-fieldname="child_test_depends_on_field"]').as('table'); | ||||
cy.get('@table').find('button.grid-add-row').click(); | |||||
cy.get('@table').findByRole('button', {name: 'Add Row'}).click(); | |||||
cy.get('@table').find('[data-idx="1"]').as('row1'); | cy.get('@table').find('[data-idx="1"]').as('row1'); | ||||
cy.get('@row1').find('.btn-open-row').click(); | cy.get('@row1').find('.btn-open-row').click(); | ||||
cy.get('@row1').find('.form-in-grid').as('row1-form_in_grid'); | cy.get('@row1').find('.form-in-grid').as('row1-form_in_grid'); | ||||
@@ -25,7 +25,7 @@ context('FileUploader', () => { | |||||
cy.get_open_dialog().find('.file-name').should('contain', 'example.json'); | cy.get_open_dialog().find('.file-name').should('contain', 'example.json'); | ||||
cy.intercept('POST', '/api/method/upload_file').as('upload_file'); | cy.intercept('POST', '/api/method/upload_file').as('upload_file'); | ||||
cy.get_open_dialog().find('.btn-modal-primary').click(); | |||||
cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click(); | |||||
cy.wait('@upload_file').its('response.statusCode').should('eq', 200); | cy.wait('@upload_file').its('response.statusCode').should('eq', 200); | ||||
cy.get('.modal:visible').should('not.exist'); | cy.get('.modal:visible').should('not.exist'); | ||||
}); | }); | ||||
@@ -33,11 +33,11 @@ context('FileUploader', () => { | |||||
it('should accept uploaded files', () => { | it('should accept uploaded files', () => { | ||||
open_upload_dialog(); | open_upload_dialog(); | ||||
cy.get_open_dialog().find('.btn-file-upload div:contains("Library")').click(); | |||||
cy.get('.file-filter').type('example.json'); | |||||
cy.get_open_dialog().find('.tree-label:contains("example.json")').first().click(); | |||||
cy.get_open_dialog().findByRole('button', {name: 'Library'}).click(); | |||||
cy.findByPlaceholderText('Search by filename or extension').type('example.json'); | |||||
cy.get_open_dialog().findAllByText('example.json').first().click(); | |||||
cy.intercept('POST', '/api/method/upload_file').as('upload_file'); | cy.intercept('POST', '/api/method/upload_file').as('upload_file'); | ||||
cy.get_open_dialog().find('.btn-primary').click(); | |||||
cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click(); | |||||
cy.wait('@upload_file').its('response.body.message') | cy.wait('@upload_file').its('response.body.message') | ||||
.should('have.property', 'file_name', 'example.json'); | .should('have.property', 'file_name', 'example.json'); | ||||
cy.get('.modal:visible').should('not.exist'); | cy.get('.modal:visible').should('not.exist'); | ||||
@@ -46,12 +46,33 @@ context('FileUploader', () => { | |||||
it('should accept web links', () => { | it('should accept web links', () => { | ||||
open_upload_dialog(); | open_upload_dialog(); | ||||
cy.get_open_dialog().find('.btn-file-upload div:contains("Link")').click(); | |||||
cy.get_open_dialog().find('.file-web-link input').type('https://github.com', { delay: 100, force: true }); | |||||
cy.get_open_dialog().findByRole('button', {name: 'Link'}).click(); | |||||
cy.get_open_dialog() | |||||
.findByPlaceholderText('Attach a web link') | |||||
.type('https://github.com', { delay: 100, force: true }); | |||||
cy.intercept('POST', '/api/method/upload_file').as('upload_file'); | cy.intercept('POST', '/api/method/upload_file').as('upload_file'); | ||||
cy.get_open_dialog().find('.btn-primary').click(); | |||||
cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click(); | |||||
cy.wait('@upload_file').its('response.body.message') | cy.wait('@upload_file').its('response.body.message') | ||||
.should('have.property', 'file_url', 'https://github.com'); | .should('have.property', 'file_url', 'https://github.com'); | ||||
cy.get('.modal:visible').should('not.exist'); | cy.get('.modal:visible').should('not.exist'); | ||||
}); | }); | ||||
it('should allow cropping and optimization for valid images', () => { | |||||
open_upload_dialog(); | |||||
cy.get_open_dialog().find('.file-upload-area').attachFile('sample_image.jpg', { | |||||
subjectType: 'drag-n-drop', | |||||
}); | |||||
cy.get_open_dialog().findAllByText('sample_image.jpg').should('exist'); | |||||
cy.get_open_dialog().find('.btn-crop').first().click(); | |||||
cy.get_open_dialog().findByRole('button', {name: 'Crop'}).click(); | |||||
cy.get_open_dialog().findAllByRole('checkbox', {name: 'Optimize'}).should('exist'); | |||||
cy.get_open_dialog().findAllByLabelText('Optimize').first().click(); | |||||
cy.intercept('POST', '/api/method/upload_file').as('upload_file'); | |||||
cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click(); | |||||
cy.wait('@upload_file').its('response.statusCode').should('eq', 200); | |||||
cy.get('.modal:visible').should('not.exist'); | |||||
}); | |||||
}); | }); |
@@ -0,0 +1,79 @@ | |||||
context('Folder Navigation', () => { | |||||
before(() => { | |||||
cy.visit('/login'); | |||||
cy.login(); | |||||
cy.visit('/app/file'); | |||||
}); | |||||
it('Adding Folders', () => { | |||||
//Adding filter to go into the home folder | |||||
cy.get('.filter-selector > .btn').findByText('1 filter').click(); | |||||
cy.findByRole('button', {name: 'Clear Filters'}).click(); | |||||
cy.get('.filter-action-buttons > .text-muted').findByText('+ Add a Filter').click(); | |||||
cy.get('.fieldname-select-area > .awesomplete > .form-control').type('Fol{enter}'); | |||||
cy.get('.filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback').type('Home{enter}'); | |||||
cy.get('.filter-action-buttons > div > .btn-primary').findByText('Apply Filters').click(); | |||||
//Adding folder (Test Folder) | |||||
cy.get('.menu-btn-group > .btn').click(); | |||||
cy.get('.menu-btn-group [data-label="New Folder"]').click(); | |||||
cy.get('form > [data-fieldname="value"]').type('Test Folder'); | |||||
cy.findByRole('button', {name: 'Create'}).click(); | |||||
}); | |||||
it('Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct', () => { | |||||
//Navigating inside the Attachments folder | |||||
cy.get('[title="Attachments"] > span').click(); | |||||
//To check if the URL formed after visiting the attachments folder is correct | |||||
cy.location('pathname').should('eq', '/app/file/view/home/Attachments'); | |||||
cy.visit('/app/file/view/home/Attachments'); | |||||
//Adding folder inside the attachments folder | |||||
cy.get('.menu-btn-group > .btn').click(); | |||||
cy.get('.menu-btn-group [data-label="New Folder"]').click(); | |||||
cy.get('form > [data-fieldname="value"]').type('Test Folder'); | |||||
cy.findByRole('button', {name: 'Create'}).click(); | |||||
//Navigating inside the added folder in the Attachments folder | |||||
cy.get('[title="Test Folder"] > span').click(); | |||||
//To check if the URL is correct after visiting the Test Folder | |||||
cy.location('pathname').should('eq', '/app/file/view/home/Attachments/Test%20Folder'); | |||||
cy.visit('/app/file/view/home/Attachments/Test%20Folder'); | |||||
//Adding a file inside the Test Folder | |||||
cy.findByRole('button', {name: 'Add File'}).eq(0).click({force: true}); | |||||
cy.get('.file-uploader').findByText('Link').click(); | |||||
cy.get('.input-group > .form-control').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg'); | |||||
cy.findByRole('button', {name: 'Upload'}).click(); | |||||
//To check if the added file is present in the Test Folder | |||||
cy.get('span.level-item > span').should('contain', 'Test Folder'); | |||||
cy.get('.list-row-container').eq(0).should('contain.text', '72402.jpg'); | |||||
cy.get('.list-row-checkbox').eq(0).click(); | |||||
//Deleting the added file from the Test folder | |||||
cy.findByRole('button', {name: 'Actions'}).click(); | |||||
cy.get('.actions-btn-group [data-label="Delete"]').click(); | |||||
cy.wait(700); | |||||
cy.findByRole('button', {name: 'Yes'}).click(); | |||||
cy.wait(700); | |||||
//Deleting the Test Folder | |||||
cy.visit('/app/file/view/home/Attachments'); | |||||
cy.get('.list-row-checkbox').eq(0).click(); | |||||
cy.findByRole('button', {name: 'Actions'}).click(); | |||||
cy.get('.actions-btn-group [data-label="Delete"]').click(); | |||||
cy.findByRole('button', {name: 'Yes'}).click(); | |||||
}); | |||||
it('Deleting Test Folder from the home', () => { | |||||
//Deleting the Test Folder added in the home directory | |||||
cy.visit('/app/file/view/home'); | |||||
cy.get('.level-left > .list-subject > .list-row-checkbox').eq(0).click({force: true, delay: 500}); | |||||
cy.findByRole('button', {name: 'Actions'}).click(); | |||||
cy.get('.actions-btn-group [data-label="Delete"]').click(); | |||||
cy.findByRole('button', {name: 'Yes'}).click(); | |||||
}); | |||||
}); |
@@ -18,6 +18,7 @@ context('Form', () => { | |||||
cy.get('.primary-action').click(); | cy.get('.primary-action').click(); | ||||
cy.wait('@form_save').its('response.statusCode').should('eq', 200); | cy.wait('@form_save').its('response.statusCode').should('eq', 200); | ||||
cy.visit('/app/todo'); | cy.visit('/app/todo'); | ||||
cy.wait(300); | |||||
cy.get('.title-text').should('be.visible').and('contain', 'To Do'); | cy.get('.title-text').should('be.visible').and('contain', 'To Do'); | ||||
cy.get('.list-row').should('contain', 'this is a test todo'); | cy.get('.list-row').should('contain', 'this is a test todo'); | ||||
}); | }); | ||||
@@ -25,7 +26,7 @@ context('Form', () => { | |||||
cy.visit('/app/contact'); | cy.visit('/app/contact'); | ||||
cy.add_filter(); | cy.add_filter(); | ||||
cy.get('.filter-field .input-with-feedback.form-control').type('123', { force: true }); | cy.get('.filter-field .input-with-feedback.form-control').type('123', { force: true }); | ||||
cy.get('.filter-popover .apply-filters').click({ force: true }); | |||||
cy.findByRole('button', {name: 'Apply Filters'}).click({ force: true }); | |||||
cy.visit('/app/contact/Test Form Contact 3'); | cy.visit('/app/contact/Test Form Contact 3'); | ||||
cy.get('.prev-doc').should('be.visible').click(); | cy.get('.prev-doc').should('be.visible').click(); | ||||
cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible'); | cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible'); | ||||
@@ -0,0 +1,88 @@ | |||||
context('Form Tour', () => { | |||||
before(() => { | |||||
cy.login(); | |||||
cy.visit('/app/form-tour'); | |||||
return cy.window().its('frappe').then(frappe => { | |||||
return frappe.call("frappe.tests.ui_test_helpers.create_form_tour"); | |||||
}); | |||||
}); | |||||
const open_test_form_tour = () => { | |||||
cy.visit('/app/form-tour/Test Form Tour'); | |||||
cy.findByRole('button', {name: 'Show Tour'}).should('be.visible').as('show_tour'); | |||||
cy.get('@show_tour').click(); | |||||
cy.wait(500); | |||||
cy.url().should('include', '/app/contact'); | |||||
}; | |||||
it('jump to a form tour', open_test_form_tour); | |||||
it('navigates a form tour', () => { | |||||
open_test_form_tour(); | |||||
cy.get('.frappe-driver').should('be.visible'); | |||||
cy.get('.frappe-control[data-fieldname="first_name"]').as('first_name'); | |||||
cy.get('@first_name').should('have.class', 'driver-highlighted-element'); | |||||
cy.get('.frappe-driver').findByRole('button', {name: 'Next'}).as('next_btn'); | |||||
// next btn shouldn't move to next step, if first name is not entered | |||||
cy.get('@next_btn').click(); | |||||
cy.wait(500); | |||||
cy.get('@first_name').should('have.class', 'driver-highlighted-element'); | |||||
// after filling the field, next step should be highlighted | |||||
cy.fill_field('first_name', 'Test Name', 'Data'); | |||||
cy.wait(500); | |||||
cy.get('@next_btn').click(); | |||||
cy.wait(500); | |||||
// assert field is highlighted | |||||
cy.get('.frappe-control[data-fieldname="last_name"]').as('last_name'); | |||||
cy.get('@last_name').should('have.class', 'driver-highlighted-element'); | |||||
// after filling the field, next step should be highlighted | |||||
cy.fill_field('last_name', 'Test Last Name', 'Data'); | |||||
cy.wait(500); | |||||
cy.get('@next_btn').click(); | |||||
cy.wait(500); | |||||
// assert field is highlighted | |||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('phone_nos'); | |||||
cy.get('@phone_nos').should('have.class', 'driver-highlighted-element'); | |||||
// move to next step | |||||
cy.wait(500); | |||||
cy.get('@next_btn').click(); | |||||
cy.wait(500); | |||||
// assert add row btn is highlighted | |||||
cy.get('@phone_nos').find('.grid-add-row').as('add_row'); | |||||
cy.get('@add_row').should('have.class', 'driver-highlighted-element'); | |||||
// add a row & move to next step | |||||
cy.wait(500); | |||||
cy.get('@add_row').click(); | |||||
cy.wait(500); | |||||
// assert table field is highlighted | |||||
cy.get('.grid-row-open .frappe-control[data-fieldname="phone"]').as('phone'); | |||||
cy.get('@phone').should('have.class', 'driver-highlighted-element'); | |||||
// enter value in a table field | |||||
let field = cy.fill_table_field('phone_nos', '1', 'phone', '1234567890'); | |||||
field.blur(); | |||||
// move to collapse row step | |||||
cy.wait(500); | |||||
cy.get('.driver-popover-title').contains('Test Title 4').siblings().get('@next_btn').click(); | |||||
cy.wait(500); | |||||
// collapse row | |||||
cy.get('.grid-row-open .grid-collapse-row').click(); | |||||
cy.wait(500); | |||||
// assert save btn is highlighted | |||||
cy.get('.primary-action').should('have.class', 'driver-highlighted-element'); | |||||
cy.wait(500); | |||||
cy.get('.frappe-driver').findByRole('button', {name: 'Save'}).should('be.visible'); | |||||
}); | |||||
}); |
@@ -30,12 +30,12 @@ context('Grid Pagination', () => { | |||||
it('adds and deletes rows and changes page', () => { | it('adds and deletes rows and changes page', () => { | ||||
cy.visit('/app/contact/Test Contact'); | cy.visit('/app/contact/Test Contact'); | ||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | ||||
cy.get('@table').find('button.grid-add-row').click(); | |||||
cy.get('@table').findByRole('button', {name: 'Add Row'}).click(); | |||||
cy.get('@table').find('.grid-body .row-index').should('contain', 1001); | cy.get('@table').find('.grid-body .row-index').should('contain', 1001); | ||||
cy.get('@table').find('.current-page-number').should('contain', '21'); | cy.get('@table').find('.current-page-number').should('contain', '21'); | ||||
cy.get('@table').find('.total-page-number').should('contain', '21'); | cy.get('@table').find('.total-page-number').should('contain', '21'); | ||||
cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({ force: true }); | cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({ force: true }); | ||||
cy.get('@table').find('button.grid-remove-rows').click(); | |||||
cy.get('@table').findByRole('button', {name: 'Delete'}).click(); | |||||
cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000); | cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000); | ||||
cy.get('@table').find('.current-page-number').should('contain', '20'); | cy.get('@table').find('.current-page-number').should('contain', '20'); | ||||
cy.get('@table').find('.total-page-number').should('contain', '20'); | cy.get('@table').find('.total-page-number').should('contain', '20'); | ||||
@@ -7,11 +7,11 @@ context('List View', () => { | |||||
}); | }); | ||||
}); | }); | ||||
it('enables "Actions" button', () => { | it('enables "Actions" button', () => { | ||||
const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete']; | |||||
const actions = ['Approve', 'Reject', 'Edit', 'Export', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete']; | |||||
cy.go_to_list('ToDo'); | cy.go_to_list('ToDo'); | ||||
cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({ multiple: true, force: true }); | cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({ multiple: true, force: true }); | ||||
cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click(); | cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click(); | ||||
cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 8).each((el, index) => { | |||||
cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 9).each((el, index) => { | |||||
cy.wrap(el).contains(actions[index]); | cy.wrap(el).contains(actions[index]); | ||||
}).then((elements) => { | }).then((elements) => { | ||||
cy.intercept({ | cy.intercept({ | ||||
@@ -17,9 +17,9 @@ context('List View Settings', () => { | |||||
cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click(); | cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click(); | ||||
cy.get('.modal-dialog').should('contain', 'DocType Settings'); | cy.get('.modal-dialog').should('contain', 'DocType Settings'); | ||||
cy.get('input[data-fieldname="disable_count"]').check({ force: true }); | |||||
cy.get('input[data-fieldname="disable_sidebar_stats"]').check({ force: true }); | |||||
cy.get('button').filter(':visible').contains('Save').click(); | |||||
cy.findByLabelText('Disable Count').check({ force: true }); | |||||
cy.findByLabelText('Disable Sidebar Stats').check({ force: true }); | |||||
cy.findByRole('button', {name: 'Save'}).click(); | |||||
cy.reload({ force: true }); | cy.reload({ force: true }); | ||||
@@ -29,8 +29,8 @@ context('List View Settings', () => { | |||||
cy.get('.menu-btn-group button').click({ force: true }); | cy.get('.menu-btn-group button').click({ force: true }); | ||||
cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click(); | cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click(); | ||||
cy.get('.modal-dialog').should('contain', 'DocType Settings'); | cy.get('.modal-dialog').should('contain', 'DocType Settings'); | ||||
cy.get('input[data-fieldname="disable_count"]').uncheck({ force: true }); | |||||
cy.get('input[data-fieldname="disable_sidebar_stats"]').uncheck({ force: true }); | |||||
cy.get('button').filter(':visible').contains('Save').click(); | |||||
cy.findByLabelText('Disable Count').uncheck({ force: true }); | |||||
cy.findByLabelText('Disable Sidebar Stats').uncheck({ force: true }); | |||||
cy.findByRole('button', {name: 'Save'}).click(); | |||||
}); | }); | ||||
}); | }); |
@@ -11,13 +11,13 @@ context('Login', () => { | |||||
it('validates password', () => { | it('validates password', () => { | ||||
cy.get('#login_email').type('Administrator'); | cy.get('#login_email').type('Administrator'); | ||||
cy.get('.btn-login:visible').click(); | |||||
cy.findByRole('button', {name: 'Login'}).click(); | |||||
cy.location('pathname').should('eq', '/login'); | cy.location('pathname').should('eq', '/login'); | ||||
}); | }); | ||||
it('validates email', () => { | it('validates email', () => { | ||||
cy.get('#login_password').type('qwe'); | cy.get('#login_password').type('qwe'); | ||||
cy.get('.btn-login:visible').click(); | |||||
cy.findByRole('button', {name: 'Login'}).click(); | |||||
cy.location('pathname').should('eq', '/login'); | cy.location('pathname').should('eq', '/login'); | ||||
}); | }); | ||||
@@ -25,8 +25,8 @@ context('Login', () => { | |||||
cy.get('#login_email').type('Administrator'); | cy.get('#login_email').type('Administrator'); | ||||
cy.get('#login_password').type('qwer'); | cy.get('#login_password').type('qwer'); | ||||
cy.get('.btn-login:visible').click(); | |||||
cy.get('.btn-login:visible').contains('Invalid Login. Try again.'); | |||||
cy.findByRole('button', {name: 'Login'}).click(); | |||||
cy.findByRole('button', {name: 'Invalid Login. Try again.'}).should('exist'); | |||||
cy.location('pathname').should('eq', '/login'); | cy.location('pathname').should('eq', '/login'); | ||||
}); | }); | ||||
@@ -34,7 +34,7 @@ context('Login', () => { | |||||
cy.get('#login_email').type('Administrator'); | cy.get('#login_email').type('Administrator'); | ||||
cy.get('#login_password').type(Cypress.config('adminPassword')); | cy.get('#login_password').type(Cypress.config('adminPassword')); | ||||
cy.get('.btn-login:visible').click(); | |||||
cy.findByRole('button', {name: 'Login'}).click(); | |||||
cy.location('pathname').should('eq', '/app'); | cy.location('pathname').should('eq', '/app'); | ||||
cy.window().its('frappe.session.user').should('eq', 'Administrator'); | cy.window().its('frappe.session.user').should('eq', 'Administrator'); | ||||
}); | }); | ||||
@@ -60,7 +60,7 @@ context('Login', () => { | |||||
cy.get('#login_email').type('Administrator'); | cy.get('#login_email').type('Administrator'); | ||||
cy.get('#login_password').type(Cypress.config('adminPassword')); | cy.get('#login_password').type(Cypress.config('adminPassword')); | ||||
cy.get('.btn-login:visible').click(); | |||||
cy.findByRole('button', {name: 'Login'}).click(); | |||||
// verify redirected location and url params after login | // verify redirected location and url params after login | ||||
cy.url().should('include', '/me?' + payload.toString().replace('+', '%20')); | cy.url().should('include', '/me?' + payload.toString().replace('+', '%20')); | ||||
@@ -0,0 +1,14 @@ | |||||
context('Navigation', () => { | |||||
before(() => { | |||||
cy.login(); | |||||
cy.visit('/app/website'); | |||||
}); | |||||
it('Navigate to route with hash in document name', () => { | |||||
cy.insert_doc('ToDo', {'__newname': 'ABC#123', 'description': 'Test this', 'ignore_duplicate': true}); | |||||
cy.visit('/app/todo/ABC#123'); | |||||
cy.title().should('eq', 'Test this - ABC#123'); | |||||
cy.get_field('description', 'Text Editor').contains('Test this'); | |||||
cy.go('back'); | |||||
cy.title().should('eq', 'Website'); | |||||
}); | |||||
}); |
@@ -16,24 +16,24 @@ context('Recorder', () => { | |||||
it('Navigate to Recorder', () => { | it('Navigate to Recorder', () => { | ||||
cy.visit('/app'); | cy.visit('/app'); | ||||
cy.awesomebar('recorder'); | cy.awesomebar('recorder'); | ||||
cy.get('h3').should('contain', 'Recorder'); | |||||
cy.findByTitle('Recorder').should('exist'); | |||||
cy.url().should('include', '/recorder/detail'); | cy.url().should('include', '/recorder/detail'); | ||||
}); | }); | ||||
it('Recorder Empty State', () => { | it('Recorder Empty State', () => { | ||||
cy.get('.title-text').should('contain', 'Recorder'); | |||||
cy.findByTitle('Recorder').should('exist'); | |||||
cy.get('.indicator-pill').should('contain', 'Inactive').should('have.class', 'red'); | cy.get('.indicator-pill').should('contain', 'Inactive').should('have.class', 'red'); | ||||
cy.get('.primary-action').should('contain', 'Start'); | |||||
cy.get('.btn-secondary').should('contain', 'Clear'); | |||||
cy.findByRole('button', {name: 'Start'}).should('exist'); | |||||
cy.findByRole('button', {name: 'Clear'}).should('exist'); | |||||
cy.get('.msg-box').should('contain', 'Inactive'); | cy.get('.msg-box').should('contain', 'Inactive'); | ||||
cy.get('.msg-box .btn-primary').should('contain', 'Start Recording'); | |||||
cy.findByRole('button', {name: 'Start Recording'}).should('exist'); | |||||
}); | }); | ||||
it('Recorder Start', () => { | it('Recorder Start', () => { | ||||
cy.get('.primary-action').should('contain', 'Start').click(); | |||||
cy.findByRole('button', {name: 'Start'}).click(); | |||||
cy.get('.indicator-pill').should('contain', 'Active').should('have.class', 'green'); | cy.get('.indicator-pill').should('contain', 'Active').should('have.class', 'green'); | ||||
cy.get('.msg-box').should('contain', 'No Requests'); | cy.get('.msg-box').should('contain', 'No Requests'); | ||||
@@ -46,12 +46,12 @@ context('Recorder', () => { | |||||
cy.get('.list-count').should('contain', '20 of '); | cy.get('.list-count').should('contain', '20 of '); | ||||
cy.visit('/app/recorder'); | cy.visit('/app/recorder'); | ||||
cy.get('.title-text').should('contain', 'Recorder'); | |||||
cy.findByTitle('Recorder').should('exist'); | |||||
cy.get('.result-list').should('contain', '/api/method/frappe.desk.reportview.get'); | cy.get('.result-list').should('contain', '/api/method/frappe.desk.reportview.get'); | ||||
}); | }); | ||||
it('Recorder View Request', () => { | it('Recorder View Request', () => { | ||||
cy.get('.primary-action').should('contain', 'Start').click(); | |||||
cy.findByRole('button', {name: 'Start'}).click(); | |||||
cy.visit('/app/List/DocType/List'); | cy.visit('/app/List/DocType/List'); | ||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh'); | cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh'); | ||||
@@ -23,7 +23,7 @@ context('Report View', () => { | |||||
let cell = cy.get('.dt-row-0 > .dt-cell--col-4'); | let cell = cy.get('.dt-row-0 > .dt-cell--col-4'); | ||||
// select the cell | // select the cell | ||||
cell.dblclick(); | cell.dblclick(); | ||||
cell.find('input[data-fieldname="enabled"]').check({ force: true }); | |||||
cell.findByRole('checkbox').check({ force: true }); | |||||
cy.get('.dt-row-0 > .dt-cell--col-5').click(); | cy.get('.dt-row-0 > .dt-cell--col-5').click(); | ||||
cy.wait('@value-update'); | cy.wait('@value-update'); | ||||
cy.get('@doc').then(doc => { | cy.get('@doc').then(doc => { | ||||
@@ -0,0 +1,56 @@ | |||||
context('Sidebar', () => { | |||||
before(() => { | |||||
cy.visit('/login'); | |||||
cy.login(); | |||||
cy.visit('/app/doctype'); | |||||
}); | |||||
it('Test for checking "Assigned To" counter value, adding filter and adding & removing an assignment', () => { | |||||
cy.click_sidebar_button("Assigned To"); | |||||
//To check if no filter is available in "Assigned To" dropdown | |||||
cy.get('.empty-state').should('contain', 'No filters found'); | |||||
cy.click_sidebar_button("Created By"); | |||||
//To check if "Created By" dropdown contains filter | |||||
cy.get('.group-by-item > .dropdown-item').should('contain', 'Me'); | |||||
//Assigning a doctype to a user | |||||
cy.click_listview_row_item(0); | |||||
cy.get('.form-assignments > .flex > .text-muted').click(); | |||||
cy.get_field('assign_to_me', 'Check').click(); | |||||
cy.get('.modal-footer > .standard-actions > .btn-primary').click(); | |||||
cy.visit('/app/doctype'); | |||||
cy.click_sidebar_button("Assigned To"); | |||||
//To check if filter is added in "Assigned To" dropdown after assignment | |||||
cy.get('.group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item').should('contain', '1'); | |||||
//To check if there is no filter added to the listview | |||||
cy.get('.filter-selector > .btn').should('contain', 'Filter'); | |||||
//To add a filter to display data into the listview | |||||
cy.get('.group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item').click(); | |||||
//To check if filter is applied | |||||
cy.click_filter_button().should('contain', '1 filter'); | |||||
cy.get('.fieldname-select-area > .awesomplete > .form-control').should('have.value', 'Assigned To'); | |||||
cy.get('.condition').should('have.value', 'like'); | |||||
cy.get('.filter-field > .form-group > .input-with-feedback').should('have.value', '%Administrator%'); | |||||
cy.click_filter_button(); | |||||
//To remove the applied filter | |||||
cy.clear_filters(); | |||||
//To remove the assignment | |||||
cy.visit('/app/doctype'); | |||||
cy.click_listview_row_item(0); | |||||
cy.get('.assignments > .avatar-group > .avatar > .avatar-frame').click(); | |||||
cy.get('.remove-btn').click({force: true}); | |||||
cy.hide_dialog(); | |||||
cy.visit('/app/doctype'); | |||||
cy.click_sidebar_button("Assigned To"); | |||||
cy.get('.empty-state').should('contain', 'No filters found'); | |||||
}); | |||||
}); |
@@ -9,6 +9,7 @@ context('Table MultiSelect', () => { | |||||
cy.new_form('Assignment Rule'); | cy.new_form('Assignment Rule'); | ||||
cy.fill_field('__newname', name); | cy.fill_field('__newname', name); | ||||
cy.fill_field('document_type', 'Blog Post'); | cy.fill_field('document_type', 'Blog Post'); | ||||
cy.get('.section-head').contains('Assignment Rules').scrollIntoView(); | |||||
cy.fill_field('assign_condition', 'status=="Open"', 'Code'); | cy.fill_field('assign_condition', 'status=="Open"', 'Code'); | ||||
cy.get('input[data-fieldname="users"]').focus().as('input'); | cy.get('input[data-fieldname="users"]').focus().as('input'); | ||||
cy.get('input[data-fieldname="users"] + ul').should('be.visible'); | cy.get('input[data-fieldname="users"] + ul').should('be.visible'); | ||||
@@ -0,0 +1,94 @@ | |||||
import custom_submittable_doctype from '../fixtures/custom_submittable_doctype'; | |||||
context('Timeline', () => { | |||||
before(() => { | |||||
cy.visit('/login'); | |||||
cy.login(); | |||||
}); | |||||
it('Adding new ToDo, adding new comment, verifying comment addition & deletion and deleting ToDo', () => { | |||||
//Adding new ToDo | |||||
cy.visit('/app/todo'); | |||||
cy.click_listview_primary_button('Add ToDo'); | |||||
cy.findByRole('button', {name: 'Edit in full page'}).click(); | |||||
cy.get('[data-fieldname="description"] .ql-editor').eq(0).type('Test ToDo', {force: true}); | |||||
cy.wait(200); | |||||
cy.findByRole('button', {name: 'Save'}).click(); | |||||
cy.wait(700); | |||||
cy.visit('/app/todo'); | |||||
cy.get('.level-item.ellipsis').eq(0).click(); | |||||
//To check if the comment box is initially empty and tying some text into it | |||||
cy.get('[data-fieldname="comment"] .ql-editor').should('contain', '').type('Testing Timeline'); | |||||
//Adding new comment | |||||
cy.findByRole('button', {name: 'Comment'}).click(); | |||||
//To check if the commented text is visible in the timeline content | |||||
cy.get('.timeline-content').should('contain', 'Testing Timeline'); | |||||
//Editing comment | |||||
cy.click_timeline_action_btn("Edit"); | |||||
cy.get('.timeline-content [data-fieldname="comment"] .ql-editor').first().type(' 123'); | |||||
cy.click_timeline_action_btn("Save"); | |||||
//To check if the edited comment text is visible in timeline content | |||||
cy.get('.timeline-content').should('contain', 'Testing Timeline 123'); | |||||
//Discarding comment | |||||
cy.click_timeline_action_btn("Edit"); | |||||
cy.findByRole('button', {name: 'Dismiss'}).click(); | |||||
//To check if after discarding the timeline content is same as previous | |||||
cy.get('.timeline-content').should('contain', 'Testing Timeline 123'); | |||||
//Deleting the added comment | |||||
cy.get('.actions > .btn > .icon').first().click(); | |||||
cy.findByRole('button', {name: 'Yes'}).click(); | |||||
cy.click_modal_primary_button('Yes'); | |||||
//Deleting the added ToDo | |||||
cy.get('.menu-btn-group button').eq(1).click(); | |||||
cy.get('.menu-btn-group [data-label="Delete"]').click(); | |||||
cy.findByRole('button', {name: 'Yes'}).click(); | |||||
}); | |||||
it('Timeline should have submit and cancel activity information', () => { | |||||
cy.visit('/app/doctype'); | |||||
//Creating custom doctype | |||||
cy.insert_doc('DocType', custom_submittable_doctype, true); | |||||
cy.visit('/app/custom-submittable-doctype'); | |||||
cy.click_listview_primary_button('Add Custom Submittable DocType'); | |||||
//Adding a new entry for the created custom doctype | |||||
cy.fill_field('title', 'Test'); | |||||
cy.findByRole('button', {name: 'Save'}).click(); | |||||
cy.findByRole('button', {name: 'Submit'}).click(); | |||||
cy.visit('/app/custom-submittable-doctype'); | |||||
cy.get('.list-subject > .bold > .ellipsis').eq(0).click(); | |||||
//To check if the submission of the documemt is visible in the timeline content | |||||
cy.get('.timeline-content').should('contain', 'Administrator submitted this document'); | |||||
cy.findByRole('button', {name: 'Cancel'}).click({delay: 900}); | |||||
cy.findByRole('button', {name: 'Yes'}).click(); | |||||
//To check if the cancellation of the documemt is visible in the timeline content | |||||
cy.get('.timeline-content').should('contain', 'Administrator cancelled this document'); | |||||
//Deleting the document | |||||
cy.visit('/app/custom-submittable-doctype'); | |||||
cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click(); | |||||
cy.findByRole('button', {name: 'Actions'}).click(); | |||||
cy.get('.actions-btn-group > .dropdown-menu > li > .dropdown-item').contains("Delete").click(); | |||||
cy.click_modal_primary_button('Yes', {force: true, delay: 700}); | |||||
//Deleting the custom doctype | |||||
cy.visit('/app/doctype'); | |||||
cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click(); | |||||
cy.findByRole('button', {name: 'Actions'}).click(); | |||||
cy.get('.actions-btn-group [data-label="Delete"]').click(); | |||||
cy.click_modal_primary_button('Yes'); | |||||
}); | |||||
}); |
@@ -0,0 +1,70 @@ | |||||
context('Timeline Email', () => { | |||||
before(() => { | |||||
cy.visit('/login'); | |||||
cy.login(); | |||||
cy.visit('/app/todo'); | |||||
}); | |||||
it('Adding new ToDo, adding email and verifying timeline content for email attachment, deleting attachment and ToDo', () => { | |||||
//Adding new ToDo | |||||
cy.click_listview_primary_button('Add ToDo'); | |||||
cy.get('.custom-actions:visible > .btn').contains("Edit in full page").click({delay: 500}); | |||||
cy.fill_field("description", "Test ToDo", "Text Editor"); | |||||
cy.wait(500); | |||||
cy.get('.primary-action').contains('Save').click({force: true}); | |||||
cy.wait(700); | |||||
cy.visit('/app/todo'); | |||||
cy.get('.list-row > .level-left > .list-subject').eq(0).click(); | |||||
//Creating a new email | |||||
cy.get('.timeline-actions > .btn').click(); | |||||
cy.fill_field('recipients', 'test@example.com', 'MultiSelect'); | |||||
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-body > :nth-child(1) > .form-layout > .form-page > :nth-child(3) > .section-body > .form-column > form > [data-fieldtype="Text Editor"] > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor').type('Test Mail'); | |||||
//Adding attachment to the email | |||||
cy.get('.add-more-attachments > .btn').click(); | |||||
cy.get('.mt-2 > .btn > .mt-1').eq(2).click(); | |||||
cy.get('.input-group > .form-control').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg'); | |||||
cy.get('.btn-primary').contains('Upload').click(); | |||||
//Sending the email | |||||
cy.click_modal_primary_button('Send', {delay: 500}); | |||||
//To check if the sent mail content is shown in the timeline content | |||||
cy.get('[data-doctype="Communication"] > .timeline-content').should('contain', 'Test Mail'); | |||||
//To check if the attachment of email is shown in the timeline content | |||||
cy.get('.timeline-content').should('contain', 'Added 72402.jpg'); | |||||
//Deleting the sent email | |||||
cy.get('[title="Open Communication"] > .icon').first().click({force: true}); | |||||
cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn').click(); | |||||
cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .dropdown-menu > li > .grey-link').eq(9).click(); | |||||
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').click(); | |||||
cy.visit('/app/todo'); | |||||
cy.get('.list-row > .level-left > .list-subject > .level-item.ellipsis > .ellipsis').eq(0).click(); | |||||
//Removing the added attachment | |||||
cy.get('.attachment-row > .data-pill > .remove-btn > .icon').click(); | |||||
cy.get('.modal-footer:visible > .standard-actions > .btn-primary').contains('Yes').click(); | |||||
//To check if the removed attachment is shown in the timeline content | |||||
cy.get('.timeline-content').should('contain', 'Removed 72402.jpg'); | |||||
cy.wait(500); | |||||
//To check if the discard button functionality in email is working correctly | |||||
cy.get('.timeline-actions > .btn').click(); | |||||
cy.fill_field('recipients', 'test@example.com', 'MultiSelect'); | |||||
cy.get('.modal-footer > .standard-actions > .btn-secondary').contains('Discard').click(); | |||||
cy.wait(500); | |||||
cy.get('.timeline-actions > .btn').click(); | |||||
cy.wait(500); | |||||
cy.get_field('recipients', 'MultiSelect').should('have.text', ''); | |||||
cy.get('.modal-header:visible > .modal-actions > .btn-modal-close > .icon').click(); | |||||
//Deleting the added ToDo | |||||
cy.get('.menu-btn-group:visible > .btn').click(); | |||||
cy.get('.menu-btn-group:visible > .dropdown-menu > li > .dropdown-item').contains('Delete').click(); | |||||
cy.get('.modal-footer:visible > .standard-actions > .btn-primary').click(); | |||||
}); | |||||
}); |
@@ -0,0 +1,90 @@ | |||||
context('Workspace 2.0', () => { | |||||
before(() => { | |||||
cy.visit('/login'); | |||||
cy.login(); | |||||
cy.visit('/app/website'); | |||||
}); | |||||
it('Navigate to page from sidebar', () => { | |||||
cy.visit('/app/build'); | |||||
cy.get('.codex-editor__redactor .ce-block'); | |||||
cy.get('.sidebar-item-container[item-name="Settings"]').first().click(); | |||||
cy.location('pathname').should('eq', '/app/settings'); | |||||
}); | |||||
it('Create Private Page', () => { | |||||
cy.get('.codex-editor__redactor .ce-block'); | |||||
cy.get('.custom-actions button[data-label="Create%20Workspace"]').click(); | |||||
cy.fill_field('title', 'Test Private Page', 'Data'); | |||||
cy.fill_field('icon', 'edit', 'Icon'); | |||||
cy.get_open_dialog().find('.modal-header').click(); | |||||
cy.get_open_dialog().find('.btn-primary').click(); | |||||
// check if sidebar item is added in pubic section | |||||
cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('have.attr', 'item-public', '0'); | |||||
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click(); | |||||
cy.wait(300); | |||||
cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('have.attr', 'item-public', '0'); | |||||
cy.wait(500); | |||||
cy.get('.codex-editor__redactor .ce-block'); | |||||
cy.get('.standard-actions .btn-secondary[data-label=Edit]').click(); | |||||
}); | |||||
it('Add New Block', () => { | |||||
cy.get('.codex-editor__redactor .ce-block'); | |||||
cy.get('.custom-actions .inner-group-button[data-label="Add%20Block"]').click(); | |||||
cy.get('.custom-actions .inner-group-button .dropdown-menu .block-menu-item-label').contains('Heading').click(); | |||||
cy.get(":focus").type('Header'); | |||||
cy.get(".ce-block:last").find('.ce-header').should('exist'); | |||||
cy.get('.custom-actions .inner-group-button[data-label="Add%20Block"]').click(); | |||||
cy.get('.custom-actions .inner-group-button .dropdown-menu .block-menu-item-label').contains('Text').click(); | |||||
cy.get(":focus").type('Paragraph text'); | |||||
cy.get(".ce-block:last").find('.ce-paragraph').should('exist'); | |||||
}); | |||||
it('Delete A Block', () => { | |||||
cy.get(".ce-block:last").find('.delete-paragraph').click(); | |||||
cy.get(".ce-block:last").find('.ce-paragraph').should('not.exist'); | |||||
}); | |||||
it('Shrink and Expand A Block', () => { | |||||
cy.get(".ce-block:last").find('.tune-btn').click(); | |||||
cy.get('.ce-settings--opened .ce-shrink-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-11'); | |||||
cy.get('.ce-settings--opened .ce-shrink-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-10'); | |||||
cy.get('.ce-settings--opened .ce-shrink-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-9'); | |||||
cy.get('.ce-settings--opened .ce-expand-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-10'); | |||||
cy.get('.ce-settings--opened .ce-expand-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-11'); | |||||
cy.get('.ce-settings--opened .ce-expand-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-12'); | |||||
}); | |||||
it('Change Header Text Size', () => { | |||||
cy.get('.ce-settings--opened .cdx-settings-button[data-level="3"]').click(); | |||||
cy.get(".ce-block:last").find('.widget-head h3').should('exist'); | |||||
cy.get('.ce-settings--opened .cdx-settings-button[data-level="4"]').click(); | |||||
cy.get(".ce-block:last").find('.widget-head h4').should('exist'); | |||||
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click(); | |||||
}); | |||||
it('Delete Private Page', () => { | |||||
cy.get('.codex-editor__redactor .ce-block'); | |||||
cy.get('.standard-actions .btn-secondary[data-label=Edit]').click(); | |||||
cy.get('.sidebar-item-container[item-name="Test Private Page"]').find('.sidebar-item-control .delete-page').click(); | |||||
cy.wait(300); | |||||
cy.get('.modal-footer > .standard-actions > .btn-modal-primary:visible').first().click(); | |||||
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click(); | |||||
cy.get('.codex-editor__redactor .ce-block'); | |||||
cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('not.exist'); | |||||
}); | |||||
}); |
@@ -1,4 +1,5 @@ | |||||
import 'cypress-file-upload'; | import 'cypress-file-upload'; | ||||
import '@testing-library/cypress/add-commands'; | |||||
// *********************************************** | // *********************************************** | ||||
// This example commands.js shows you how to | // This example commands.js shows you how to | ||||
// create various custom commands and overwrite | // create various custom commands and overwrite | ||||
@@ -186,22 +187,22 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => { | |||||
if (fieldtype === 'Select') { | if (fieldtype === 'Select') { | ||||
cy.get('@input').select(value); | cy.get('@input').select(value); | ||||
} else { | } else { | ||||
cy.get('@input').type(value, {waitForAnimations: false, force: true}); | |||||
cy.get('@input').type(value, {waitForAnimations: false, force: true, delay: 100}); | |||||
} | } | ||||
return cy.get('@input'); | return cy.get('@input'); | ||||
}); | }); | ||||
Cypress.Commands.add('get_field', (fieldname, fieldtype = 'Data') => { | Cypress.Commands.add('get_field', (fieldname, fieldtype = 'Data') => { | ||||
let selector = `.form-control[data-fieldname="${fieldname}"]`; | |||||
let selector = `[data-fieldname="${fieldname}"] input:visible`; | |||||
if (fieldtype === 'Text Editor') { | if (fieldtype === 'Text Editor') { | ||||
selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`; | |||||
selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]:visible`; | |||||
} | } | ||||
if (fieldtype === 'Code') { | if (fieldtype === 'Code') { | ||||
selector = `[data-fieldname="${fieldname}"] .ace_text-input`; | selector = `[data-fieldname="${fieldname}"] .ace_text-input`; | ||||
} | } | ||||
return cy.get(selector); | |||||
return cy.get(selector).first(); | |||||
}); | }); | ||||
Cypress.Commands.add('fill_table_field', (tablefieldname, row_idx, fieldname, value, fieldtype = 'Data') => { | Cypress.Commands.add('fill_table_field', (tablefieldname, row_idx, fieldname, value, fieldtype = 'Data') => { | ||||
@@ -251,7 +252,8 @@ Cypress.Commands.add('new_form', doctype => { | |||||
}); | }); | ||||
Cypress.Commands.add('go_to_list', doctype => { | Cypress.Commands.add('go_to_list', doctype => { | ||||
cy.visit(`/app/list/${doctype}/list`); | |||||
let dt_in_route = doctype.toLowerCase().replace(/ /g, '-'); | |||||
cy.visit(`/app/${dt_in_route}`); | |||||
}); | }); | ||||
Cypress.Commands.add('clear_cache', () => { | Cypress.Commands.add('clear_cache', () => { | ||||
@@ -315,7 +317,11 @@ Cypress.Commands.add('add_filter', () => { | |||||
}); | }); | ||||
Cypress.Commands.add('clear_filters', () => { | Cypress.Commands.add('clear_filters', () => { | ||||
cy.get('.filter-section .filter-button').click(); | |||||
cy.intercept({ | |||||
method: 'POST', | |||||
url: 'api/method/frappe.model.utils.user_settings.save' | |||||
}).as('filter-saved'); | |||||
cy.get('.filter-section .filter-button').click({force: true}); | |||||
cy.wait(300); | cy.wait(300); | ||||
cy.get('.filter-popover').should('exist'); | cy.get('.filter-popover').should('exist'); | ||||
cy.get('.filter-popover').find('.clear-filters').click(); | cy.get('.filter-popover').find('.clear-filters').click(); | ||||
@@ -323,4 +329,29 @@ Cypress.Commands.add('clear_filters', () => { | |||||
cy.window().its('cur_list').then(cur_list => { | cy.window().its('cur_list').then(cur_list => { | ||||
cur_list && cur_list.filter_area && cur_list.filter_area.clear(); | cur_list && cur_list.filter_area && cur_list.filter_area.clear(); | ||||
}); | }); | ||||
cy.wait('@filter-saved'); | |||||
}); | |||||
Cypress.Commands.add('click_modal_primary_button', (btn_name) => { | |||||
cy.get('.modal-footer > .standard-actions > .btn-primary').contains(btn_name).trigger('click', {force: true}); | |||||
}); | |||||
Cypress.Commands.add('click_sidebar_button', (btn_name) => { | |||||
cy.get('.list-group-by-fields .list-link > a').contains(btn_name).click({force: true}); | |||||
}); | |||||
Cypress.Commands.add('click_listview_row_item', (row_no) => { | |||||
cy.get('.list-row > .level-left > .list-subject > .bold > .ellipsis').eq(row_no).click({force: true}); | |||||
}); | |||||
Cypress.Commands.add('click_filter_button', () => { | |||||
cy.get('.filter-selector > .btn').click(); | |||||
}); | |||||
Cypress.Commands.add('click_listview_primary_button', (btn_name) => { | |||||
cy.get('.primary-action').contains(btn_name).click({force: true}); | |||||
}); | }); | ||||
Cypress.Commands.add('click_timeline_action_btn', (btn_name) => { | |||||
cy.get('.timeline-content > .timeline-message-box > .justify-between > .actions > .btn').contains(btn_name).click(); | |||||
}); |
@@ -8,6 +8,7 @@ let yargs = require("yargs"); | |||||
let cliui = require("cliui")(); | let cliui = require("cliui")(); | ||||
let chalk = require("chalk"); | let chalk = require("chalk"); | ||||
let html_plugin = require("./frappe-html"); | let html_plugin = require("./frappe-html"); | ||||
let rtlcss = require('rtlcss'); | |||||
let postCssPlugin = require("esbuild-plugin-postcss2").default; | let postCssPlugin = require("esbuild-plugin-postcss2").default; | ||||
let ignore_assets = require("./ignore-assets"); | let ignore_assets = require("./ignore-assets"); | ||||
let sass_options = require("./sass_options"); | let sass_options = require("./sass_options"); | ||||
@@ -96,9 +97,9 @@ async function execute() { | |||||
await clean_dist_folders(APPS); | await clean_dist_folders(APPS); | ||||
} | } | ||||
let result; | |||||
let results; | |||||
try { | try { | ||||
result = await build_assets_for_apps(APPS, FILES_TO_BUILD); | |||||
results = await build_assets_for_apps(APPS, FILES_TO_BUILD); | |||||
} catch (e) { | } catch (e) { | ||||
log_error("There were some problems during build"); | log_error("There were some problems during build"); | ||||
log(); | log(); | ||||
@@ -107,13 +108,15 @@ async function execute() { | |||||
} | } | ||||
if (!WATCH_MODE) { | if (!WATCH_MODE) { | ||||
log_built_assets(result.metafile); | |||||
log_built_assets(results); | |||||
console.timeEnd(TOTAL_BUILD_TIME); | console.timeEnd(TOTAL_BUILD_TIME); | ||||
log(); | log(); | ||||
} else { | } else { | ||||
log("Watching for changes..."); | log("Watching for changes..."); | ||||
} | } | ||||
return await write_assets_json(result.metafile); | |||||
for (const result of results) { | |||||
await write_assets_json(result.metafile); | |||||
} | |||||
} | } | ||||
function build_assets_for_apps(apps, files) { | function build_assets_for_apps(apps, files) { | ||||
@@ -125,6 +128,8 @@ function build_assets_for_apps(apps, files) { | |||||
let output_path = assets_path; | let output_path = assets_path; | ||||
let file_map = {}; | let file_map = {}; | ||||
let style_file_map = {}; | |||||
let rtl_style_file_map = {}; | |||||
for (let file of files) { | for (let file of files) { | ||||
let relative_app_path = path.relative(apps_path, file); | let relative_app_path = path.relative(apps_path, file); | ||||
let app = relative_app_path.split(path.sep)[0]; | let app = relative_app_path.split(path.sep)[0]; | ||||
@@ -140,19 +145,32 @@ function build_assets_for_apps(apps, files) { | |||||
} | } | ||||
output_name = path.join(app, "dist", output_name); | output_name = path.join(app, "dist", output_name); | ||||
if (Object.keys(file_map).includes(output_name)) { | |||||
if (Object.keys(file_map).includes(output_name) || Object.keys(style_file_map).includes(output_name)) { | |||||
log_warn( | log_warn( | ||||
`Duplicate output file ${output_name} generated from ${file}` | `Duplicate output file ${output_name} generated from ${file}` | ||||
); | ); | ||||
} | } | ||||
file_map[output_name] = file; | |||||
if ([".css", ".scss", ".less", ".sass", ".styl"].includes(extension)) { | |||||
style_file_map[output_name] = file; | |||||
rtl_style_file_map[output_name.replace('/css/', '/css-rtl/')] = file; | |||||
} else { | |||||
file_map[output_name] = file; | |||||
} | |||||
} | } | ||||
return build_files({ | |||||
let build = build_files({ | |||||
files: file_map, | files: file_map, | ||||
outdir: output_path | outdir: output_path | ||||
}); | }); | ||||
let style_build = build_style_files({ | |||||
files: style_file_map, | |||||
outdir: output_path | |||||
}); | |||||
let rtl_style_build = build_style_files({ | |||||
files: rtl_style_file_map, | |||||
outdir: output_path, | |||||
rtl_style: true | |||||
}); | |||||
return Promise.all([build, style_build, rtl_style_build]); | |||||
}); | }); | ||||
} | } | ||||
@@ -203,7 +221,33 @@ function get_files_to_build(files) { | |||||
} | } | ||||
function build_files({ files, outdir }) { | function build_files({ files, outdir }) { | ||||
return esbuild.build({ | |||||
let build_plugins = [ | |||||
html_plugin, | |||||
vue(), | |||||
]; | |||||
return esbuild.build(get_build_options(files, outdir, build_plugins)); | |||||
} | |||||
function build_style_files({ files, outdir, rtl_style=false }) { | |||||
let plugins = []; | |||||
if (rtl_style) { | |||||
plugins.push(rtlcss); | |||||
} | |||||
let build_plugins = [ | |||||
ignore_assets, | |||||
postCssPlugin({ | |||||
plugins: plugins, | |||||
sassOptions: sass_options | |||||
}) | |||||
]; | |||||
plugins.push(require("autoprefixer")); | |||||
return esbuild.build(get_build_options(files, outdir, build_plugins)); | |||||
} | |||||
function get_build_options(files, outdir, plugins) { | |||||
return { | |||||
entryPoints: files, | entryPoints: files, | ||||
entryNames: "[dir]/[name].[hash]", | entryNames: "[dir]/[name].[hash]", | ||||
outdir, | outdir, | ||||
@@ -217,17 +261,9 @@ function build_files({ files, outdir }) { | |||||
PRODUCTION ? "production" : "development" | PRODUCTION ? "production" : "development" | ||||
) | ) | ||||
}, | }, | ||||
plugins: [ | |||||
html_plugin, | |||||
ignore_assets, | |||||
vue(), | |||||
postCssPlugin({ | |||||
plugins: [require("autoprefixer")], | |||||
sassOptions: sass_options | |||||
}) | |||||
], | |||||
plugins: plugins, | |||||
watch: get_watch_config() | watch: get_watch_config() | ||||
}); | |||||
}; | |||||
} | } | ||||
function get_watch_config() { | function get_watch_config() { | ||||
@@ -258,16 +294,26 @@ function get_watch_config() { | |||||
async function clean_dist_folders(apps) { | async function clean_dist_folders(apps) { | ||||
for (let app of apps) { | for (let app of apps) { | ||||
let public_path = get_public_path(app); | let public_path = get_public_path(app); | ||||
await fs.promises.rmdir(path.resolve(public_path, "dist", "js"), { | |||||
recursive: true | |||||
}); | |||||
await fs.promises.rmdir(path.resolve(public_path, "dist", "css"), { | |||||
recursive: true | |||||
}); | |||||
let paths = [ | |||||
path.resolve(public_path, "dist", "js"), | |||||
path.resolve(public_path, "dist", "css"), | |||||
path.resolve(public_path, "dist", "css-rtl") | |||||
]; | |||||
for (let target of paths) { | |||||
if (fs.existsSync(target)) { | |||||
// rmdir is deprecated in node 16, this will work in both node 14 and 16 | |||||
let rmdir = fs.promises.rm || fs.promises.rmdir; | |||||
await rmdir(target, { recursive: true }); | |||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
function log_built_assets(metafile) { | |||||
function log_built_assets(results) { | |||||
let outputs = {}; | |||||
for (const result of results) { | |||||
outputs = Object.assign(outputs, result.metafile.outputs); | |||||
} | |||||
let column_widths = [60, 20]; | let column_widths = [60, 20]; | ||||
cliui.div( | cliui.div( | ||||
{ | { | ||||
@@ -282,9 +328,9 @@ function log_built_assets(metafile) { | |||||
cliui.div(""); | cliui.div(""); | ||||
let output_by_dist_path = {}; | let output_by_dist_path = {}; | ||||
for (let outfile in metafile.outputs) { | |||||
for (let outfile in outputs) { | |||||
if (outfile.endsWith(".map")) continue; | if (outfile.endsWith(".map")) continue; | ||||
let data = metafile.outputs[outfile]; | |||||
let data = outputs[outfile]; | |||||
outfile = path.resolve(outfile); | outfile = path.resolve(outfile); | ||||
outfile = path.relative(assets_path, outfile); | outfile = path.relative(assets_path, outfile); | ||||
let filename = path.basename(outfile); | let filename = path.basename(outfile); | ||||
@@ -339,7 +385,11 @@ async function write_assets_json(metafile) { | |||||
let info = metafile.outputs[output]; | let info = metafile.outputs[output]; | ||||
let asset_path = "/" + path.relative(sites_path, output); | let asset_path = "/" + path.relative(sites_path, output); | ||||
if (info.entryPoint) { | if (info.entryPoint) { | ||||
out[path.basename(info.entryPoint)] = asset_path; | |||||
let key = path.basename(info.entryPoint); | |||||
if (key.endsWith('.css') && asset_path.includes('/css-rtl/')) { | |||||
key = `rtl_${key}`; | |||||
} | |||||
out[key] = asset_path; | |||||
} | } | ||||
} | } | ||||
@@ -478,4 +528,4 @@ function log_rebuilt_assets(prev_assets, new_assets) { | |||||
log(" " + filename); | log(" " + filename); | ||||
} | } | ||||
log(); | log(); | ||||
} | |||||
} |
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE | |||||
""" | """ | ||||
Frappe - Low Code Open Source Framework in Python and JS | Frappe - Low Code Open Source Framework in Python and JS | ||||
@@ -28,6 +28,8 @@ from .exceptions import * | |||||
from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader) | from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader) | ||||
from .utils.lazy_loader import lazy_import | from .utils.lazy_loader import lazy_import | ||||
from frappe.query_builder import get_query_builder, patch_query_execute | |||||
# Lazy imports | # Lazy imports | ||||
faker = lazy_import('faker') | faker = lazy_import('faker') | ||||
@@ -118,6 +120,7 @@ def set_user_lang(user, user_language=None): | |||||
# local-globals | # local-globals | ||||
db = local("db") | db = local("db") | ||||
qb = local("qb") | |||||
conf = local("conf") | conf = local("conf") | ||||
form = form_dict = local("form_dict") | form = form_dict = local("form_dict") | ||||
request = local("request") | request = local("request") | ||||
@@ -137,7 +140,11 @@ lang = local("lang") | |||||
if typing.TYPE_CHECKING: | if typing.TYPE_CHECKING: | ||||
from frappe.database.mariadb.database import MariaDBDatabase | from frappe.database.mariadb.database import MariaDBDatabase | ||||
from frappe.database.postgres.database import PostgresDatabase | from frappe.database.postgres.database import PostgresDatabase | ||||
from pypika import Query | |||||
db: typing.Union[MariaDBDatabase, PostgresDatabase] | db: typing.Union[MariaDBDatabase, PostgresDatabase] | ||||
qb: Query | |||||
# end: static analysis hack | # end: static analysis hack | ||||
def init(site, sites_path=None, new_site=False): | def init(site, sites_path=None, new_site=False): | ||||
@@ -202,8 +209,10 @@ def init(site, sites_path=None, new_site=False): | |||||
local.form_dict = _dict() | local.form_dict = _dict() | ||||
local.session = _dict() | local.session = _dict() | ||||
local.dev_server = _dev_server | local.dev_server = _dev_server | ||||
local.qb = get_query_builder(local.conf.db_type or "mariadb") | |||||
setup_module_map() | setup_module_map() | ||||
patch_query_execute() | |||||
local.initialised = True | local.initialised = True | ||||
@@ -1491,7 +1500,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, | |||||
:param style: Print Format style. | :param style: Print Format style. | ||||
:param as_pdf: Return as PDF. Default False. | :param as_pdf: Return as PDF. Default False. | ||||
:param password: Password to encrypt the pdf with. Default None""" | :param password: Password to encrypt the pdf with. Default None""" | ||||
from frappe.website.render import build_page | |||||
from frappe.website.serve import get_response_content | |||||
from frappe.utils.pdf import get_pdf | from frappe.utils.pdf import get_pdf | ||||
local.form_dict.doctype = doctype | local.form_dict.doctype = doctype | ||||
@@ -1506,7 +1515,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, | |||||
options = {'password': password} | options = {'password': password} | ||||
if not html: | if not html: | ||||
html = build_page("printview") | |||||
html = get_response_content("printview") | |||||
if as_pdf: | if as_pdf: | ||||
return get_pdf(html, output = output, options = options) | return get_pdf(html, output = output, options = options) | ||||
@@ -1683,7 +1692,7 @@ def get_desk_link(doctype, name): | |||||
) | ) | ||||
def bold(text): | def bold(text): | ||||
return '<b>{0}</b>'.format(text) | |||||
return '<strong>{0}</strong>'.format(text) | |||||
def safe_eval(code, eval_globals=None, eval_locals=None): | def safe_eval(code, eval_globals=None, eval_locals=None): | ||||
'''A safer `eval`''' | '''A safer `eval`''' | ||||
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE | |||||
import base64 | import base64 | ||||
import binascii | import binascii | ||||
import json | import json | ||||
@@ -82,7 +82,7 @@ def handle(): | |||||
if frappe.local.request.method=="PUT": | if frappe.local.request.method=="PUT": | ||||
data = get_request_form_data() | data = get_request_form_data() | ||||
doc = frappe.get_doc(doctype, name) | |||||
doc = frappe.get_doc(doctype, name, for_update=True) | |||||
if "flags" in data: | if "flags" in data: | ||||
del data["flags"] | del data["flags"] | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE | |||||
import os | import os | ||||
import logging | import logging | ||||
@@ -16,9 +16,9 @@ import frappe.handler | |||||
import frappe.auth | import frappe.auth | ||||
import frappe.api | import frappe.api | ||||
import frappe.utils.response | import frappe.utils.response | ||||
import frappe.website.render | |||||
from frappe.utils import get_site_name, sanitize_html | from frappe.utils import get_site_name, sanitize_html | ||||
from frappe.middlewares import StaticDataMiddleware | from frappe.middlewares import StaticDataMiddleware | ||||
from frappe.website.serve import get_response | |||||
from frappe.utils.error import make_error_snapshot | from frappe.utils.error import make_error_snapshot | ||||
from frappe.core.doctype.comment.comment import update_comments_in_parent_after_request | from frappe.core.doctype.comment.comment import update_comments_in_parent_after_request | ||||
from frappe import _ | from frappe import _ | ||||
@@ -72,7 +72,7 @@ def application(request): | |||||
response = frappe.utils.response.download_private_file(request.path) | response = frappe.utils.response.download_private_file(request.path) | ||||
elif request.method in ('GET', 'HEAD', 'POST'): | elif request.method in ('GET', 'HEAD', 'POST'): | ||||
response = frappe.website.render.render() | |||||
response = get_response() | |||||
else: | else: | ||||
raise NotFound | raise NotFound | ||||
@@ -266,8 +266,7 @@ def handle_exception(e): | |||||
make_error_snapshot(e) | make_error_snapshot(e) | ||||
if return_as_message: | if return_as_message: | ||||
response = frappe.website.render.render("message", | |||||
http_status_code=http_status_code) | |||||
response = get_response("message", http_status_code=http_status_code) | |||||
return response | return response | ||||
@@ -1,71 +1,82 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# MIT License. See license.txt | |||||
import datetime | |||||
from frappe import _ | |||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# MIT License. See LICENSE | |||||
from urllib.parse import quote | |||||
import frappe | import frappe | ||||
import frappe.database | import frappe.database | ||||
import frappe.utils | import frappe.utils | ||||
from frappe.utils import cint, flt, get_datetime, datetime, date_diff, today | |||||
import frappe.utils.user | import frappe.utils.user | ||||
from frappe import conf | |||||
from frappe.sessions import Session, clear_sessions, delete_session | |||||
from frappe.modules.patch_handler import check_session_stopped | |||||
from frappe.translate import get_lang_code | |||||
from frappe.utils.password import check_password, delete_login_failed_cache | |||||
from frappe import _, conf | |||||
from frappe.core.doctype.activity_log.activity_log import add_authentication_log | from frappe.core.doctype.activity_log.activity_log import add_authentication_log | ||||
from frappe.twofactor import (should_run_2fa, authenticate_for_2factor, | |||||
confirm_otp_token, get_cached_user_pass) | |||||
from frappe.modules.patch_handler import check_session_stopped | |||||
from frappe.sessions import Session, clear_sessions, delete_session | |||||
from frappe.translate import get_language | |||||
from frappe.twofactor import authenticate_for_2factor, confirm_otp_token, get_cached_user_pass, should_run_2fa | |||||
from frappe.utils import cint, date_diff, datetime, get_datetime, today | |||||
from frappe.utils.password import check_password | |||||
from frappe.website.utils import get_home_page | from frappe.website.utils import get_home_page | ||||
from urllib.parse import quote | |||||
class HTTPRequest: | class HTTPRequest: | ||||
def __init__(self): | def __init__(self): | ||||
# Get Environment variables | |||||
self.domain = frappe.request.host | |||||
if self.domain and self.domain.startswith('www.'): | |||||
self.domain = self.domain[4:] | |||||
if frappe.get_request_header('X-Forwarded-For'): | |||||
frappe.local.request_ip = (frappe.get_request_header('X-Forwarded-For').split(",")[0]).strip() | |||||
elif frappe.get_request_header('REMOTE_ADDR'): | |||||
frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR') | |||||
else: | |||||
frappe.local.request_ip = '127.0.0.1' | |||||
# language | |||||
self.set_lang() | |||||
# set frappe.local.request_ip | |||||
self.set_request_ip() | |||||
# load cookies | # load cookies | ||||
frappe.local.cookie_manager = CookieManager() | |||||
self.set_cookies() | |||||
# set db | |||||
# set frappe.local.db | |||||
self.connect() | self.connect() | ||||
# login | |||||
frappe.local.login_manager = LoginManager() | |||||
# login and start/resume user session | |||||
self.set_session() | |||||
if frappe.form_dict._lang: | |||||
lang = get_lang_code(frappe.form_dict._lang) | |||||
if lang: | |||||
frappe.local.lang = lang | |||||
# set request language | |||||
self.set_lang() | |||||
# match csrf token from current session | |||||
self.validate_csrf_token() | self.validate_csrf_token() | ||||
# write out latest cookies | # write out latest cookies | ||||
frappe.local.cookie_manager.init_cookies() | frappe.local.cookie_manager.init_cookies() | ||||
# check status | |||||
# check session status | |||||
check_session_stopped() | check_session_stopped() | ||||
@property | |||||
def domain(self): | |||||
if not getattr(self, "_domain", None): | |||||
self._domain = frappe.request.host | |||||
if self._domain and self._domain.startswith('www.'): | |||||
self._domain = self._domain[4:] | |||||
return self._domain | |||||
def set_request_ip(self): | |||||
if frappe.get_request_header('X-Forwarded-For'): | |||||
frappe.local.request_ip = (frappe.get_request_header('X-Forwarded-For').split(",")[0]).strip() | |||||
elif frappe.get_request_header('REMOTE_ADDR'): | |||||
frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR') | |||||
else: | |||||
frappe.local.request_ip = '127.0.0.1' | |||||
def set_cookies(self): | |||||
frappe.local.cookie_manager = CookieManager() | |||||
def set_session(self): | |||||
frappe.local.login_manager = LoginManager() | |||||
def validate_csrf_token(self): | def validate_csrf_token(self): | ||||
if frappe.local.request and frappe.local.request.method in ("POST", "PUT", "DELETE"): | if frappe.local.request and frappe.local.request.method in ("POST", "PUT", "DELETE"): | ||||
if not frappe.local.session: return | |||||
if not frappe.local.session.data.csrf_token \ | |||||
or frappe.local.session.data.device=="mobile" \ | |||||
or frappe.conf.get('ignore_csrf', None): | |||||
if not frappe.local.session: | |||||
return | |||||
if ( | |||||
not frappe.local.session.data.csrf_token | |||||
or frappe.local.session.data.device == "mobile" | |||||
or frappe.conf.get('ignore_csrf', None) | |||||
): | |||||
# not via boot | # not via boot | ||||
return | return | ||||
@@ -79,17 +90,18 @@ class HTTPRequest: | |||||
frappe.throw(_("Invalid Request"), frappe.CSRFTokenError) | frappe.throw(_("Invalid Request"), frappe.CSRFTokenError) | ||||
def set_lang(self): | def set_lang(self): | ||||
from frappe.translate import guess_language | |||||
frappe.local.lang = guess_language() | |||||
frappe.local.lang = get_language() | |||||
def get_db_name(self): | def get_db_name(self): | ||||
"""get database name from conf""" | """get database name from conf""" | ||||
return conf.db_name | return conf.db_name | ||||
def connect(self, ac_name = None): | |||||
def connect(self): | |||||
"""connect to db, from ac_name or db_name""" | """connect to db, from ac_name or db_name""" | ||||
frappe.local.db = frappe.database.get_db(user = self.get_db_name(), \ | |||||
password = getattr(conf, 'db_password', '')) | |||||
frappe.local.db = frappe.database.get_db( | |||||
user=self.get_db_name(), | |||||
password=getattr(conf, 'db_password', '') | |||||
) | |||||
class LoginManager: | class LoginManager: | ||||
def __init__(self): | def __init__(self): | ||||
@@ -143,7 +155,7 @@ class LoginManager: | |||||
self.setup_boot_cache() | self.setup_boot_cache() | ||||
self.set_user_info() | self.set_user_info() | ||||
def get_user_info(self, resume=False): | |||||
def get_user_info(self): | |||||
self.info = frappe.db.get_value("User", self.user, | self.info = frappe.db.get_value("User", self.user, | ||||
["user_type", "first_name", "last_name", "user_image"], as_dict=1) | ["user_type", "first_name", "last_name", "user_image"], as_dict=1) | ||||
@@ -181,11 +193,13 @@ class LoginManager: | |||||
frappe.local.response["redirect_to"] = redirect_to | frappe.local.response["redirect_to"] = redirect_to | ||||
frappe.cache().hdel('redirect_after_login', self.user) | frappe.cache().hdel('redirect_after_login', self.user) | ||||
frappe.local.cookie_manager.set_cookie("full_name", self.full_name) | frappe.local.cookie_manager.set_cookie("full_name", self.full_name) | ||||
frappe.local.cookie_manager.set_cookie("user_id", self.user) | frappe.local.cookie_manager.set_cookie("user_id", self.user) | ||||
frappe.local.cookie_manager.set_cookie("user_image", self.info.user_image or "") | frappe.local.cookie_manager.set_cookie("user_image", self.info.user_image or "") | ||||
def clear_preferred_language(self): | |||||
frappe.local.cookie_manager.delete_cookie("preferred_language") | |||||
def make_session(self, resume=False): | def make_session(self, resume=False): | ||||
# start session | # start session | ||||
frappe.local.session_obj = Session(user=self.user, resume=resume, | frappe.local.session_obj = Session(user=self.user, resume=resume, | ||||
@@ -72,6 +72,7 @@ | |||||
"fieldtype": "Code", | "fieldtype": "Code", | ||||
"in_list_view": 1, | "in_list_view": 1, | ||||
"label": "Assign Condition", | "label": "Assign Condition", | ||||
"options": "PythonExpression", | |||||
"reqd": 1 | "reqd": 1 | ||||
}, | }, | ||||
{ | { | ||||
@@ -82,7 +83,8 @@ | |||||
"description": "Simple Python Expression, Example: Status in (\"Closed\", \"Cancelled\")", | "description": "Simple Python Expression, Example: Status in (\"Closed\", \"Cancelled\")", | ||||
"fieldname": "unassign_condition", | "fieldname": "unassign_condition", | ||||
"fieldtype": "Code", | "fieldtype": "Code", | ||||
"label": "Unassign Condition" | |||||
"label": "Unassign Condition", | |||||
"options": "PythonExpression" | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "assign_to_users_section", | "fieldname": "assign_to_users_section", | ||||
@@ -120,7 +122,8 @@ | |||||
"description": "Simple Python Expression, Example: Status in (\"Invalid\")", | "description": "Simple Python Expression, Example: Status in (\"Invalid\")", | ||||
"fieldname": "close_condition", | "fieldname": "close_condition", | ||||
"fieldtype": "Code", | "fieldtype": "Code", | ||||
"label": "Close Condition" | |||||
"label": "Close Condition", | |||||
"options": "PythonExpression" | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "sb", | "fieldname": "sb", | ||||
@@ -151,7 +154,7 @@ | |||||
], | ], | ||||
"index_web_pages_for_search": 1, | "index_web_pages_for_search": 1, | ||||
"links": [], | "links": [], | ||||
"modified": "2020-10-20 14:47:20.662954", | |||||
"modified": "2021-07-16 22:51:35.505575", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Automation", | "module": "Automation", | ||||
"name": "Assignment Rule", | "name": "Assignment Rule", | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and contributors | # Copyright (c) 2019, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and Contributors | # Copyright (c) 2019, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
import unittest | import unittest | ||||
from frappe.utils import random_string | from frappe.utils import random_string | ||||
@@ -76,7 +76,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
# clear 5 assignments for first user | # clear 5 assignments for first user | ||||
# can't do a limit in "delete" since postgres does not support it | # can't do a limit in "delete" since postgres does not support it | ||||
for d in frappe.get_all('ToDo', dict(reference_type = 'Note', owner = 'test@example.com'), limit=5): | for d in frappe.get_all('ToDo', dict(reference_type = 'Note', owner = 'test@example.com'), limit=5): | ||||
frappe.db.sql("delete from tabToDo where name = %s", d.name) | |||||
frappe.db.delete("ToDo", {"name": d.name}) | |||||
# add 5 more assignments | # add 5 more assignments | ||||
for i in range(5): | for i in range(5): | ||||
@@ -177,7 +177,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
), 'owner'), 'test@example.com') | ), 'owner'), 'test@example.com') | ||||
def check_assignment_rule_scheduling(self): | def check_assignment_rule_scheduling(self): | ||||
frappe.db.sql("DELETE FROM `tabAssignment Rule`") | |||||
frappe.db.delete("Assignment Rule") | |||||
days_1 = [dict(day = 'Sunday'), dict(day = 'Monday'), dict(day = 'Tuesday')] | days_1 = [dict(day = 'Sunday'), dict(day = 'Monday'), dict(day = 'Tuesday')] | ||||
@@ -204,7 +204,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
), 'owner'), ['test3@example.com']) | ), 'owner'), ['test3@example.com']) | ||||
def test_assignment_rule_condition(self): | def test_assignment_rule_condition(self): | ||||
frappe.db.sql("DELETE FROM `tabAssignment Rule`") | |||||
frappe.db.delete("Assignment Rule") | |||||
# Add expiry_date custom field | # Add expiry_date custom field | ||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field | from frappe.custom.doctype.custom_field.custom_field import create_custom_field | ||||
@@ -253,7 +253,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
assignment_rule.delete() | assignment_rule.delete() | ||||
def clear_assignments(): | def clear_assignments(): | ||||
frappe.db.sql("delete from tabToDo where reference_type = 'Note'") | |||||
frappe.db.delete("ToDo", {"reference_type": "Note"}) | |||||
def get_assignment_rule(days, assign=None): | def get_assignment_rule(days, assign=None): | ||||
frappe.delete_doc_if_exists('Assignment Rule', 'For Note 1') | frappe.delete_doc_if_exists('Assignment Rule', 'For Note 1') | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and contributors | # Copyright (c) 2019, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
# import frappe | # import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and contributors | # Copyright (c) 2019, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
# import frappe | # import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -30,7 +30,7 @@ frappe.ui.form.on('Auto Repeat', { | |||||
refresh: function(frm) { | refresh: function(frm) { | ||||
// auto repeat message | // auto repeat message | ||||
if (frm.is_new()) { | if (frm.is_new()) { | ||||
let customize_form_link = `<a href="/app/customize form">${__('Customize Form')}</a>`; | |||||
let customize_form_link = `<a href="/app/customize-form">${__('Customize Form')}</a>`; | |||||
frm.dashboard.set_headline(__('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [customize_form_link])); | frm.dashboard.set_headline(__('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [customize_form_link])); | ||||
} | } | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors | # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe import _ | from frappe import _ | ||||
@@ -333,7 +333,7 @@ class AutoRepeat(Document): | |||||
if self.reference_doctype and self.reference_document: | if self.reference_doctype and self.reference_document: | ||||
res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id']) | res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id']) | ||||
res += get_contacts_linked_from(self.reference_doctype, self.reference_document, fields=['email_id']) | res += get_contacts_linked_from(self.reference_doctype, self.reference_document, fields=['email_id']) | ||||
email_ids = list(set([d.email_id for d in res])) | |||||
email_ids = {d.email_id for d in res} | |||||
if not email_ids: | if not email_ids: | ||||
frappe.msgprint(_('No contacts linked to document'), alert=True) | frappe.msgprint(_('No contacts linked to document'), alert=True) | ||||
else: | else: | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2018, Frappe Technologies and Contributors | # Copyright (c) 2018, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
import unittest | import unittest | ||||
import frappe | import frappe | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2020, Frappe Technologies and contributors | # Copyright (c) 2020, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
# import frappe | # import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and contributors | # Copyright (c) 2019, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and Contributors | # Copyright (c) 2019, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
#import frappe | #import frappe | ||||
import unittest | import unittest | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and contributors | # Copyright (c) 2019, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,13 +1,13 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and Contributors | # Copyright (c) 2019, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
import frappe.cache_manager | import frappe.cache_manager | ||||
import unittest | import unittest | ||||
class TestMilestoneTracker(unittest.TestCase): | class TestMilestoneTracker(unittest.TestCase): | ||||
def test_milestone(self): | def test_milestone(self): | ||||
frappe.db.sql('delete from `tabMilestone Tracker`') | |||||
frappe.db.delete("Milestone Tracker") | |||||
frappe.cache().delete_key('milestone_tracker_map') | frappe.cache().delete_key('milestone_tracker_map') | ||||
@@ -44,5 +44,5 @@ class TestMilestoneTracker(unittest.TestCase): | |||||
self.assertEqual(milestones[0].value, 'Closed') | self.assertEqual(milestones[0].value, 'Closed') | ||||
# cleanup | # cleanup | ||||
frappe.db.sql('delete from tabMilestone') | |||||
frappe.db.delete("Milestone") | |||||
milestone_tracker.delete() | milestone_tracker.delete() |
@@ -1,22 +1,27 @@ | |||||
{ | { | ||||
"category": "Administration", | |||||
"category": "", | |||||
"charts": [], | "charts": [], | ||||
"content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"ToDo\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Note\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"File\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Assignment Rule\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Auto Repeat\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Email\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Automation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Event Streaming\", \"col\": 4}}]", | |||||
"creation": "2020-03-02 14:53:24.980279", | "creation": "2020-03-02 14:53:24.980279", | ||||
"developer_mode_only": 0, | "developer_mode_only": 0, | ||||
"disable_user_customization": 0, | "disable_user_customization": 0, | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "Workspace", | "doctype": "Workspace", | ||||
"extends": "", | |||||
"extends_another_page": 0, | "extends_another_page": 0, | ||||
"for_user": "", | |||||
"hide_custom": 0, | "hide_custom": 0, | ||||
"icon": "tool", | "icon": "tool", | ||||
"idx": 0, | "idx": 0, | ||||
"is_standard": 1, | |||||
"is_default": 0, | |||||
"is_standard": 0, | |||||
"label": "Tools", | "label": "Tools", | ||||
"links": [ | "links": [ | ||||
{ | { | ||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Tools", | "label": "Tools", | ||||
"link_count": 0, | |||||
"onboard": 0, | "onboard": 0, | ||||
"type": "Card Break" | "type": "Card Break" | ||||
}, | }, | ||||
@@ -25,6 +30,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "To Do", | "label": "To Do", | ||||
"link_count": 0, | |||||
"link_to": "ToDo", | "link_to": "ToDo", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 1, | "onboard": 1, | ||||
@@ -35,6 +41,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Calendar", | "label": "Calendar", | ||||
"link_count": 0, | |||||
"link_to": "Event", | "link_to": "Event", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 1, | "onboard": 1, | ||||
@@ -45,6 +52,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Note", | "label": "Note", | ||||
"link_count": 0, | |||||
"link_to": "Note", | "link_to": "Note", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 1, | "onboard": 1, | ||||
@@ -55,6 +63,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Files", | "label": "Files", | ||||
"link_count": 0, | |||||
"link_to": "File", | "link_to": "File", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 0, | "onboard": 0, | ||||
@@ -65,6 +74,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Activity", | "label": "Activity", | ||||
"link_count": 0, | |||||
"link_to": "activity", | "link_to": "activity", | ||||
"link_type": "Page", | "link_type": "Page", | ||||
"onboard": 0, | "onboard": 0, | ||||
@@ -74,6 +84,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Email", | "label": "Email", | ||||
"link_count": 0, | |||||
"onboard": 0, | "onboard": 0, | ||||
"type": "Card Break" | "type": "Card Break" | ||||
}, | }, | ||||
@@ -82,6 +93,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Newsletter", | "label": "Newsletter", | ||||
"link_count": 0, | |||||
"link_to": "Newsletter", | "link_to": "Newsletter", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 1, | "onboard": 1, | ||||
@@ -92,6 +104,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Email Group", | "label": "Email Group", | ||||
"link_count": 0, | |||||
"link_to": "Email Group", | "link_to": "Email Group", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 0, | "onboard": 0, | ||||
@@ -101,6 +114,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Automation", | "label": "Automation", | ||||
"link_count": 0, | |||||
"onboard": 0, | "onboard": 0, | ||||
"type": "Card Break" | "type": "Card Break" | ||||
}, | }, | ||||
@@ -109,6 +123,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Assignment Rule", | "label": "Assignment Rule", | ||||
"link_count": 0, | |||||
"link_to": "Assignment Rule", | "link_to": "Assignment Rule", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 0, | "onboard": 0, | ||||
@@ -119,6 +134,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Milestone", | "label": "Milestone", | ||||
"link_count": 0, | |||||
"link_to": "Milestone", | "link_to": "Milestone", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 0, | "onboard": 0, | ||||
@@ -129,6 +145,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Auto Repeat", | "label": "Auto Repeat", | ||||
"link_count": 0, | |||||
"link_to": "Auto Repeat", | "link_to": "Auto Repeat", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 0, | "onboard": 0, | ||||
@@ -138,6 +155,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Event Streaming", | "label": "Event Streaming", | ||||
"link_count": 0, | |||||
"onboard": 0, | "onboard": 0, | ||||
"type": "Card Break" | "type": "Card Break" | ||||
}, | }, | ||||
@@ -146,6 +164,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Event Producer", | "label": "Event Producer", | ||||
"link_count": 0, | |||||
"link_to": "Event Producer", | "link_to": "Event Producer", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 0, | "onboard": 0, | ||||
@@ -156,6 +175,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Event Consumer", | "label": "Event Consumer", | ||||
"link_count": 0, | |||||
"link_to": "Event Consumer", | "link_to": "Event Consumer", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 0, | "onboard": 0, | ||||
@@ -166,6 +186,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Event Update Log", | "label": "Event Update Log", | ||||
"link_count": 0, | |||||
"link_to": "Event Update Log", | "link_to": "Event Update Log", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 0, | "onboard": 0, | ||||
@@ -176,6 +197,7 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Event Sync Log", | "label": "Event Sync Log", | ||||
"link_count": 0, | |||||
"link_to": "Event Sync Log", | "link_to": "Event Sync Log", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 0, | "onboard": 0, | ||||
@@ -186,19 +208,26 @@ | |||||
"hidden": 0, | "hidden": 0, | ||||
"is_query_report": 0, | "is_query_report": 0, | ||||
"label": "Document Type Mapping", | "label": "Document Type Mapping", | ||||
"link_count": 0, | |||||
"link_to": "Document Type Mapping", | "link_to": "Document Type Mapping", | ||||
"link_type": "DocType", | "link_type": "DocType", | ||||
"onboard": 0, | "onboard": 0, | ||||
"type": "Link" | "type": "Link" | ||||
} | } | ||||
], | ], | ||||
"modified": "2020-12-01 13:38:39.950350", | |||||
"modified": "2021-08-05 12:16:02.839180", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Automation", | "module": "Automation", | ||||
"name": "Tools", | "name": "Tools", | ||||
"onboarding": "", | |||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"parent_page": "", | |||||
"pin_to_bottom": 0, | "pin_to_bottom": 0, | ||||
"pin_to_top": 0, | "pin_to_top": 0, | ||||
"public": 1, | |||||
"restrict_to_domain": "", | |||||
"roles": [], | |||||
"sequence_id": 26, | |||||
"shortcuts": [ | "shortcuts": [ | ||||
{ | { | ||||
"label": "ToDo", | "label": "ToDo", | ||||
@@ -225,5 +254,6 @@ | |||||
"link_to": "Auto Repeat", | "link_to": "Auto Repeat", | ||||
"type": "DocType" | "type": "DocType" | ||||
} | } | ||||
] | |||||
], | |||||
"title": "Tools" | |||||
} | } |
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE | |||||
""" | """ | ||||
bootstrap client session | bootstrap client session | ||||
""" | """ | ||||
@@ -105,8 +105,8 @@ def load_conf_settings(bootinfo): | |||||
if key in conf: bootinfo[key] = conf.get(key) | if key in conf: bootinfo[key] = conf.get(key) | ||||
def load_desktop_data(bootinfo): | def load_desktop_data(bootinfo): | ||||
from frappe.desk.desktop import get_desk_sidebar_items | |||||
bootinfo.allowed_workspaces = get_desk_sidebar_items() | |||||
from frappe.desk.desktop import get_wspace_sidebar_items | |||||
bootinfo.allowed_workspaces = get_wspace_sidebar_items().get('pages') | |||||
bootinfo.module_page_map = get_controller("Workspace").get_module_page_map() | bootinfo.module_page_map = get_controller("Workspace").get_module_page_map() | ||||
bootinfo.dashboards = frappe.get_all("Dashboard") | bootinfo.dashboards = frappe.get_all("Dashboard") | ||||
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE | |||||
import os | import os | ||||
import re | import re | ||||
import json | import json | ||||
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe, json | import frappe, json | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -53,7 +53,7 @@ def clear_domain_cache(user=None): | |||||
cache.delete_value(domain_cache_keys) | cache.delete_value(domain_cache_keys) | ||||
def clear_global_cache(): | def clear_global_cache(): | ||||
from frappe.website.render import clear_cache as clear_website_cache | |||||
from frappe.website.utils import clear_website_cache | |||||
clear_doctype_cache() | clear_doctype_cache() | ||||
clear_website_cache() | clear_website_cache() | ||||
@@ -141,18 +141,13 @@ def build_table_count_cache(): | |||||
return | return | ||||
_cache = frappe.cache() | _cache = frappe.cache() | ||||
data = frappe.db.multisql({ | |||||
"mariadb": """ | |||||
SELECT table_name AS name, | |||||
table_rows AS count | |||||
FROM information_schema.tables""", | |||||
"postgres": """ | |||||
SELECT "relname" AS name, | |||||
"n_tup_ins" AS count | |||||
FROM "pg_stat_all_tables" | |||||
""" | |||||
}, as_dict=1) | |||||
table_name = frappe.qb.Field("table_name").as_("name") | |||||
table_rows = frappe.qb.Field("table_rows").as_("count") | |||||
information_schema = frappe.qb.Schema("information_schema") | |||||
data = ( | |||||
frappe.qb.from_(information_schema.tables).select(table_name, table_rows) | |||||
).run(as_dict=True) | |||||
counts = {d.get('name').lstrip('tab'): d.get('count', None) for d in data} | counts = {d.get('name').lstrip('tab'): d.get('count', None) for d in data} | ||||
_cache.set_value("information_schema:counts", counts) | _cache.set_value("information_schema:counts", counts) | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2018, Frappe Technologies and contributors | # Copyright (c) 2018, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe import _ | from frappe import _ | ||||
import frappe.model | import frappe.model | ||||
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE | |||||
import sys | import sys | ||||
import click | import click | ||||
@@ -102,7 +102,9 @@ def get_commands(): | |||||
from .site import commands as site_commands | from .site import commands as site_commands | ||||
from .translate import commands as translate_commands | from .translate import commands as translate_commands | ||||
from .utils import commands as utils_commands | from .utils import commands as utils_commands | ||||
from .redis import commands as redis_commands | |||||
return list(set(scheduler_commands + site_commands + translate_commands + utils_commands)) | |||||
all_commands = scheduler_commands + site_commands + translate_commands + utils_commands + redis_commands | |||||
return list(set(all_commands)) | |||||
commands = get_commands() | commands = get_commands() |
@@ -0,0 +1,53 @@ | |||||
import os | |||||
import click | |||||
import frappe | |||||
from frappe.utils.rq import RedisQueue | |||||
from frappe.installer import update_site_config | |||||
@click.command('create-rq-users') | |||||
@click.option('--set-admin-password', is_flag=True, default=False, help='Set new Redis admin(default user) password') | |||||
@click.option('--use-rq-auth', is_flag=True, default=False, help='Enable Redis authentication for sites') | |||||
def create_rq_users(set_admin_password=False, use_rq_auth=False): | |||||
"""Create Redis Queue users and add to acl and app configs. | |||||
acl config file will be used by redis server while starting the server | |||||
and app config is used by app while connecting to redis server. | |||||
""" | |||||
acl_file_path = os.path.abspath('../config/redis_queue.acl') | |||||
with frappe.init_site(): | |||||
acl_list, user_credentials = RedisQueue.gen_acl_list( | |||||
set_admin_password=set_admin_password) | |||||
with open(acl_file_path, 'w') as f: | |||||
f.writelines([acl+'\n' for acl in acl_list]) | |||||
sites_path = os.getcwd() | |||||
common_site_config_path = os.path.join(sites_path, 'common_site_config.json') | |||||
update_site_config("rq_username", user_credentials['bench'][0], validate=False, | |||||
site_config_path=common_site_config_path) | |||||
update_site_config("rq_password", user_credentials['bench'][1], validate=False, | |||||
site_config_path=common_site_config_path) | |||||
update_site_config("use_rq_auth", use_rq_auth, validate=False, | |||||
site_config_path=common_site_config_path) | |||||
click.secho('* ACL and site configs are updated with new user credentials. ' | |||||
'Please restart Redis Queue server to enable namespaces.', | |||||
fg='green') | |||||
if set_admin_password: | |||||
env_key = 'RQ_ADMIN_PASWORD' | |||||
click.secho('* Redis admin password is successfully set up. ' | |||||
'Include below line in .bashrc file for system to use', | |||||
fg='green') | |||||
click.secho(f"`export {env_key}={user_credentials['default'][1]}`") | |||||
click.secho('NOTE: Please save the admin password as you ' | |||||
'can not access redis server without the password', | |||||
fg='yellow') | |||||
commands = [ | |||||
create_rq_users | |||||
] |
@@ -172,9 +172,13 @@ def start_scheduler(): | |||||
@click.command('worker') | @click.command('worker') | ||||
@click.option('--queue', type=str) | @click.option('--queue', type=str) | ||||
@click.option('--quiet', is_flag = True, default = False, help = 'Hide Log Outputs') | @click.option('--quiet', is_flag = True, default = False, help = 'Hide Log Outputs') | ||||
def start_worker(queue, quiet = False): | |||||
@click.option('-u', '--rq-username', default=None, help='Redis ACL user') | |||||
@click.option('-p', '--rq-password', default=None, help='Redis ACL user password') | |||||
def start_worker(queue, quiet = False, rq_username=None, rq_password=None): | |||||
"""Site is used to find redis credentals. | |||||
""" | |||||
from frappe.utils.background_jobs import start_worker | from frappe.utils.background_jobs import start_worker | ||||
start_worker(queue, quiet = quiet) | |||||
start_worker(queue, quiet = quiet, rq_username=rq_username, rq_password=rq_password) | |||||
@click.command('ready-for-migration') | @click.command('ready-for-migration') | ||||
@click.option('--site', help='site name') | @click.option('--site', help='site name') | ||||
@@ -193,7 +193,7 @@ def install_app(context, apps): | |||||
print("App {} is Incompatible with Site {}{}".format(app, site, err_msg)) | print("App {} is Incompatible with Site {}{}".format(app, site, err_msg)) | ||||
exit_code = 1 | exit_code = 1 | ||||
except Exception as err: | except Exception as err: | ||||
err_msg = ":\n{}".format(err if str(err) else frappe.get_traceback()) | |||||
err_msg = ": {}\n{}".format(str(err), frappe.get_traceback()) | |||||
print("An error occurred while installing {}{}".format(app, err_msg)) | print("An error occurred while installing {}{}".format(app, err_msg)) | ||||
exit_code = 1 | exit_code = 1 | ||||
@@ -561,30 +561,54 @@ def move(dest_dir, site): | |||||
return final_new_path | return final_new_path | ||||
@click.command('set-password') | |||||
@click.argument('user') | |||||
@click.argument('password', required=False) | |||||
@click.option('--logout-all-sessions', help='Logout from all sessions', is_flag=True, default=False) | |||||
@pass_context | |||||
def set_password(context, user, password=None, logout_all_sessions=False): | |||||
"Set password for a user on a site" | |||||
if not context.sites: | |||||
raise SiteNotSpecifiedError | |||||
for site in context.sites: | |||||
set_user_password(site, user, password, logout_all_sessions) | |||||
@click.command('set-admin-password') | @click.command('set-admin-password') | ||||
@click.argument('admin-password') | |||||
@click.argument('admin-password', required=False) | |||||
@click.option('--logout-all-sessions', help='Logout from all sessions', is_flag=True, default=False) | @click.option('--logout-all-sessions', help='Logout from all sessions', is_flag=True, default=False) | ||||
@pass_context | @pass_context | ||||
def set_admin_password(context, admin_password, logout_all_sessions=False): | |||||
def set_admin_password(context, admin_password=None, logout_all_sessions=False): | |||||
"Set Administrator password for a site" | "Set Administrator password for a site" | ||||
if not context.sites: | |||||
raise SiteNotSpecifiedError | |||||
for site in context.sites: | |||||
set_user_password(site, "Administrator", admin_password, logout_all_sessions) | |||||
def set_user_password(site, user, password, logout_all_sessions=False): | |||||
import getpass | import getpass | ||||
from frappe.utils.password import update_password | from frappe.utils.password import update_password | ||||
for site in context.sites: | |||||
try: | |||||
frappe.init(site=site) | |||||
try: | |||||
frappe.init(site=site) | |||||
while not admin_password: | |||||
admin_password = getpass.getpass("Administrator's password for {0}: ".format(site)) | |||||
while not password: | |||||
password = getpass.getpass(f"{user}'s password for {site}: ") | |||||
frappe.connect() | |||||
if not frappe.db.exists("User", user): | |||||
print(f"User {user} does not exist") | |||||
sys.exit(1) | |||||
update_password(user=user, pwd=password, logout_all_sessions=logout_all_sessions) | |||||
frappe.db.commit() | |||||
password = None | |||||
finally: | |||||
frappe.destroy() | |||||
frappe.connect() | |||||
update_password(user='Administrator', pwd=admin_password, logout_all_sessions=logout_all_sessions) | |||||
frappe.db.commit() | |||||
admin_password = None | |||||
finally: | |||||
frappe.destroy() | |||||
if not context.sites: | |||||
raise SiteNotSpecifiedError | |||||
@click.command('set-last-active-for-user') | @click.command('set-last-active-for-user') | ||||
@click.option('--user', help="Setup last active date for user") | @click.option('--user', help="Setup last active date for user") | ||||
@@ -729,6 +753,7 @@ commands = [ | |||||
remove_from_installed_apps, | remove_from_installed_apps, | ||||
restore, | restore, | ||||
run_patch, | run_patch, | ||||
set_password, | |||||
set_admin_password, | set_admin_password, | ||||
uninstall, | uninstall, | ||||
disable_user, | disable_user, | ||||
@@ -1,5 +1,3 @@ | |||||
# -*- coding: utf-8 -*- | |||||
import json | import json | ||||
import os | import os | ||||
import subprocess | import subprocess | ||||
@@ -11,7 +9,14 @@ import click | |||||
import frappe | import frappe | ||||
from frappe.commands import get_site, pass_context | from frappe.commands import get_site, pass_context | ||||
from frappe.exceptions import SiteNotSpecifiedError | from frappe.exceptions import SiteNotSpecifiedError | ||||
from frappe.utils import get_bench_path, update_progress_bar, cint | |||||
from frappe.utils import update_progress_bar, cint | |||||
from frappe.coverage import CodeCoverage | |||||
DATA_IMPORT_DEPRECATION = click.style( | |||||
"[DEPRECATED] The `import-csv` command used 'Data Import Legacy' which has been deprecated.\n" | |||||
"Use `data-import` command instead to import data via 'Data Import'.", | |||||
fg="yellow" | |||||
) | |||||
@click.command('build') | @click.command('build') | ||||
@@ -69,14 +74,14 @@ def watch(apps=None): | |||||
def clear_cache(context): | def clear_cache(context): | ||||
"Clear cache, doctype cache and defaults" | "Clear cache, doctype cache and defaults" | ||||
import frappe.sessions | import frappe.sessions | ||||
import frappe.website.render | |||||
from frappe.website.utils import clear_website_cache | |||||
from frappe.desk.notifications import clear_notifications | from frappe.desk.notifications import clear_notifications | ||||
for site in context.sites: | for site in context.sites: | ||||
try: | try: | ||||
frappe.connect(site) | frappe.connect(site) | ||||
frappe.clear_cache() | frappe.clear_cache() | ||||
clear_notifications() | clear_notifications() | ||||
frappe.website.render.clear_cache() | |||||
clear_website_cache() | |||||
finally: | finally: | ||||
frappe.destroy() | frappe.destroy() | ||||
if not context.sites: | if not context.sites: | ||||
@@ -86,12 +91,12 @@ def clear_cache(context): | |||||
@pass_context | @pass_context | ||||
def clear_website_cache(context): | def clear_website_cache(context): | ||||
"Clear website cache" | "Clear website cache" | ||||
import frappe.website.render | |||||
from frappe.website.utils import clear_website_cache | |||||
for site in context.sites: | for site in context.sites: | ||||
try: | try: | ||||
frappe.init(site=site) | frappe.init(site=site) | ||||
frappe.connect() | frappe.connect() | ||||
frappe.website.render.clear_cache() | |||||
clear_website_cache() | |||||
finally: | finally: | ||||
frappe.destroy() | frappe.destroy() | ||||
if not context.sites: | if not context.sites: | ||||
@@ -350,7 +355,8 @@ def import_doc(context, path, force=False): | |||||
if not context.sites: | if not context.sites: | ||||
raise SiteNotSpecifiedError | raise SiteNotSpecifiedError | ||||
@click.command('import-csv') | |||||
@click.command('import-csv', help=DATA_IMPORT_DEPRECATION) | |||||
@click.argument('path') | @click.argument('path') | ||||
@click.option('--only-insert', default=False, is_flag=True, help='Do not overwrite existing records') | @click.option('--only-insert', default=False, is_flag=True, help='Do not overwrite existing records') | ||||
@click.option('--submit-after-import', default=False, is_flag=True, help='Submit document after importing it') | @click.option('--submit-after-import', default=False, is_flag=True, help='Submit document after importing it') | ||||
@@ -358,32 +364,8 @@ def import_doc(context, path, force=False): | |||||
@click.option('--no-email', default=True, is_flag=True, help='Send email if applicable') | @click.option('--no-email', default=True, is_flag=True, help='Send email if applicable') | ||||
@pass_context | @pass_context | ||||
def import_csv(context, path, only_insert=False, submit_after_import=False, ignore_encoding_errors=False, no_email=True): | def import_csv(context, path, only_insert=False, submit_after_import=False, ignore_encoding_errors=False, no_email=True): | ||||
"Import CSV using data import" | |||||
from frappe.core.doctype.data_import_legacy import importer | |||||
from frappe.utils.csvutils import read_csv_content | |||||
site = get_site(context) | |||||
if not os.path.exists(path): | |||||
path = os.path.join('..', path) | |||||
if not os.path.exists(path): | |||||
print('Invalid path {0}'.format(path)) | |||||
sys.exit(1) | |||||
with open(path, 'r') as csvfile: | |||||
content = read_csv_content(csvfile.read()) | |||||
frappe.init(site=site) | |||||
frappe.connect() | |||||
try: | |||||
importer.upload(content, submit_after_import=submit_after_import, no_email=no_email, | |||||
ignore_encoding_errors=ignore_encoding_errors, overwrite=not only_insert, | |||||
via_console=True) | |||||
frappe.db.commit() | |||||
except Exception: | |||||
print(frappe.get_traceback()) | |||||
frappe.destroy() | |||||
click.secho(DATA_IMPORT_DEPRECATION) | |||||
sys.exit(1) | |||||
@click.command('data-import') | @click.command('data-import') | ||||
@@ -504,15 +486,26 @@ frappe.db.connect() | |||||
@click.command('console') | @click.command('console') | ||||
@click.option( | |||||
'--autoreload', | |||||
is_flag=True, | |||||
help="Reload changes to code automatically" | |||||
) | |||||
@pass_context | @pass_context | ||||
def console(context): | |||||
def console(context, autoreload=False): | |||||
"Start ipython console for a site" | "Start ipython console for a site" | ||||
site = get_site(context) | site = get_site(context) | ||||
frappe.init(site=site) | frappe.init(site=site) | ||||
frappe.connect() | frappe.connect() | ||||
frappe.local.lang = frappe.db.get_default("lang") | frappe.local.lang = frappe.db.get_default("lang") | ||||
import IPython | |||||
from IPython.terminal.embed import InteractiveShellEmbed | |||||
terminal = InteractiveShellEmbed() | |||||
if autoreload: | |||||
terminal.extension_manager.load_extension("autoreload") | |||||
terminal.run_line_magic("autoreload", "2") | |||||
all_apps = frappe.get_installed_apps() | all_apps = frappe.get_installed_apps() | ||||
failed_to_import = [] | failed_to_import = [] | ||||
@@ -527,7 +520,9 @@ def console(context): | |||||
if failed_to_import: | if failed_to_import: | ||||
print("\nFailed to import:\n{}".format(", ".join(failed_to_import))) | print("\nFailed to import:\n{}".format(", ".join(failed_to_import))) | ||||
IPython.embed(display_banner="", header="", colors="neutral") | |||||
terminal.colors = "neutral" | |||||
terminal.display_banner = False | |||||
terminal() | |||||
@click.command('run-tests') | @click.command('run-tests') | ||||
@@ -542,74 +537,39 @@ def console(context): | |||||
@click.option('--skip-test-records', is_flag=True, default=False, help="Don't create test records") | @click.option('--skip-test-records', is_flag=True, default=False, help="Don't create test records") | ||||
@click.option('--skip-before-tests', is_flag=True, default=False, help="Don't run before tests hook") | @click.option('--skip-before-tests', is_flag=True, default=False, help="Don't run before tests hook") | ||||
@click.option('--junit-xml-output', help="Destination file path for junit xml report") | @click.option('--junit-xml-output', help="Destination file path for junit xml report") | ||||
@click.option('--failfast', is_flag=True, default=False) | |||||
@click.option('--failfast', is_flag=True, default=False, help="Stop the test run on the first error or failure") | |||||
@pass_context | @pass_context | ||||
def run_tests(context, app=None, module=None, doctype=None, test=(), profile=False, | def run_tests(context, app=None, module=None, doctype=None, test=(), profile=False, | ||||
coverage=False, junit_xml_output=False, ui_tests = False, doctype_list_path=None, | coverage=False, junit_xml_output=False, ui_tests = False, doctype_list_path=None, | ||||
skip_test_records=False, skip_before_tests=False, failfast=False): | skip_test_records=False, skip_before_tests=False, failfast=False): | ||||
"Run tests" | |||||
import frappe.test_runner | |||||
tests = test | |||||
with CodeCoverage(coverage, app): | |||||
import frappe.test_runner | |||||
tests = test | |||||
site = get_site(context) | |||||
site = get_site(context) | |||||
allow_tests = frappe.get_conf(site).allow_tests | |||||
allow_tests = frappe.get_conf(site).allow_tests | |||||
if not (allow_tests or os.environ.get('CI')): | |||||
click.secho('Testing is disabled for the site!', bold=True) | |||||
click.secho('You can enable tests by entering following command:') | |||||
click.secho('bench --site {0} set-config allow_tests true'.format(site), fg='green') | |||||
return | |||||
if not (allow_tests or os.environ.get('CI')): | |||||
click.secho('Testing is disabled for the site!', bold=True) | |||||
click.secho('You can enable tests by entering following command:') | |||||
click.secho('bench --site {0} set-config allow_tests true'.format(site), fg='green') | |||||
return | |||||
frappe.init(site=site) | |||||
frappe.init(site=site) | |||||
frappe.flags.skip_before_tests = skip_before_tests | |||||
frappe.flags.skip_test_records = skip_test_records | |||||
frappe.flags.skip_before_tests = skip_before_tests | |||||
frappe.flags.skip_test_records = skip_test_records | |||||
if coverage: | |||||
from coverage import Coverage | |||||
# Generate coverage report only for app that is being tested | |||||
source_path = os.path.join(get_bench_path(), 'apps', app or 'frappe') | |||||
incl = [ | |||||
'*.py', | |||||
] | |||||
omit = [ | |||||
'*.js', | |||||
'*.xml', | |||||
'*.pyc', | |||||
'*.css', | |||||
'*.less', | |||||
'*.scss', | |||||
'*.vue', | |||||
'*.html', | |||||
'*/test_*', | |||||
'*/node_modules/*', | |||||
'*/doctype/*/*_dashboard.py', | |||||
'*/patches/*', | |||||
] | |||||
if not app or app == 'frappe': | |||||
omit.append('*/tests/*') | |||||
omit.append('*/commands/*') | |||||
cov = Coverage(source=[source_path], omit=omit, include=incl) | |||||
cov.start() | |||||
ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests, | |||||
force=context.force, profile=profile, junit_xml_output=junit_xml_output, | |||||
ui_tests=ui_tests, doctype_list_path=doctype_list_path, failfast=failfast) | |||||
if coverage: | |||||
cov.stop() | |||||
cov.save() | |||||
if len(ret.failures) == 0 and len(ret.errors) == 0: | |||||
ret = 0 | |||||
if os.environ.get('CI'): | |||||
sys.exit(ret) | |||||
ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests, | |||||
force=context.force, profile=profile, junit_xml_output=junit_xml_output, | |||||
ui_tests=ui_tests, doctype_list_path=doctype_list_path, failfast=failfast) | |||||
if len(ret.failures) == 0 and len(ret.errors) == 0: | |||||
ret = 0 | |||||
if os.environ.get('CI'): | |||||
sys.exit(ret) | |||||
@click.command('run-parallel-tests') | @click.command('run-parallel-tests') | ||||
@click.option('--app', help="For App", default='frappe') | @click.option('--app', help="For App", default='frappe') | ||||
@@ -619,13 +579,14 @@ def run_tests(context, app=None, module=None, doctype=None, test=(), profile=Fal | |||||
@click.option('--use-orchestrator', is_flag=True, help="Use orchestrator to run parallel tests") | @click.option('--use-orchestrator', is_flag=True, help="Use orchestrator to run parallel tests") | ||||
@pass_context | @pass_context | ||||
def run_parallel_tests(context, app, build_number, total_builds, with_coverage=False, use_orchestrator=False): | def run_parallel_tests(context, app, build_number, total_builds, with_coverage=False, use_orchestrator=False): | ||||
site = get_site(context) | |||||
if use_orchestrator: | |||||
from frappe.parallel_test_runner import ParallelTestWithOrchestrator | |||||
ParallelTestWithOrchestrator(app, site=site, with_coverage=with_coverage) | |||||
else: | |||||
from frappe.parallel_test_runner import ParallelTestRunner | |||||
ParallelTestRunner(app, site=site, build_number=build_number, total_builds=total_builds, with_coverage=with_coverage) | |||||
with CodeCoverage(with_coverage, app): | |||||
site = get_site(context) | |||||
if use_orchestrator: | |||||
from frappe.parallel_test_runner import ParallelTestWithOrchestrator | |||||
ParallelTestWithOrchestrator(app, site=site) | |||||
else: | |||||
from frappe.parallel_test_runner import ParallelTestRunner | |||||
ParallelTestRunner(app, site=site, build_number=build_number, total_builds=total_builds) | |||||
@click.command('run-ui-tests') | @click.command('run-ui-tests') | ||||
@click.argument('app') | @click.argument('app') | ||||
@@ -641,27 +602,29 @@ def run_ui_tests(context, app, headless=False, parallel=True, ci_build_id=None): | |||||
admin_password = frappe.get_conf(site).admin_password | admin_password = frappe.get_conf(site).admin_password | ||||
# override baseUrl using env variable | # override baseUrl using env variable | ||||
site_env = 'CYPRESS_baseUrl={}'.format(site_url) | |||||
password_env = 'CYPRESS_adminPassword={}'.format(admin_password) if admin_password else '' | |||||
site_env = f'CYPRESS_baseUrl={site_url}' | |||||
password_env = f'CYPRESS_adminPassword={admin_password}' if admin_password else '' | |||||
os.chdir(app_base_path) | os.chdir(app_base_path) | ||||
node_bin = subprocess.getoutput("npm bin") | node_bin = subprocess.getoutput("npm bin") | ||||
cypress_path = "{0}/cypress".format(node_bin) | |||||
plugin_path = "{0}/../cypress-file-upload".format(node_bin) | |||||
cypress_path = f"{node_bin}/cypress" | |||||
plugin_path = f"{node_bin}/../cypress-file-upload" | |||||
testing_library_path = f"{node_bin}/../@testing-library" | |||||
# check if cypress in path...if not, install it. | # check if cypress in path...if not, install it. | ||||
if not ( | if not ( | ||||
os.path.exists(cypress_path) | os.path.exists(cypress_path) | ||||
and os.path.exists(plugin_path) | and os.path.exists(plugin_path) | ||||
and os.path.exists(testing_library_path) | |||||
and cint(subprocess.getoutput("npm view cypress version")[:1]) >= 6 | and cint(subprocess.getoutput("npm view cypress version")[:1]) >= 6 | ||||
): | ): | ||||
# install cypress | # install cypress | ||||
click.secho("Installing Cypress...", fg="yellow") | click.secho("Installing Cypress...", fg="yellow") | ||||
frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 --no-lockfile") | |||||
frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 @testing-library/cypress@^8 --no-lockfile") | |||||
# run for headless mode | # run for headless mode | ||||
run_or_open = 'run --browser firefox --record --key 4a48f41c-11b3-425b-aa88-c58048fa69eb' if headless else 'open' | |||||
run_or_open = 'run --browser firefox --record' if headless else 'open' | |||||
command = '{site_env} {password_env} {cypress} {run_or_open}' | command = '{site_env} {password_env} {cypress} {run_or_open}' | ||||
formatted_command = command.format(site_env=site_env, password_env=password_env, cypress=cypress_path, run_or_open=run_or_open) | formatted_command = command.format(site_env=site_env, password_env=password_env, cypress=cypress_path, run_or_open=run_or_open) | ||||
@@ -669,7 +632,7 @@ def run_ui_tests(context, app, headless=False, parallel=True, ci_build_id=None): | |||||
formatted_command += ' --parallel' | formatted_command += ' --parallel' | ||||
if ci_build_id: | if ci_build_id: | ||||
formatted_command += ' --ci-build-id {}'.format(ci_build_id) | |||||
formatted_command += f' --ci-build-id {ci_build_id}' | |||||
click.secho("Running Cypress...", fg="yellow") | click.secho("Running Cypress...", fg="yellow") | ||||
frappe.commands.popen(formatted_command, cwd=app_base_path, raise_err=True) | frappe.commands.popen(formatted_command, cwd=app_base_path, raise_err=True) | ||||
@@ -767,22 +730,49 @@ def set_config(context, key, value, global_=False, parse=False, as_dict=False): | |||||
frappe.destroy() | frappe.destroy() | ||||
@click.command('version') | |||||
def get_version(): | |||||
"Show the versions of all the installed apps" | |||||
@click.command("version") | |||||
@click.option("-f", "--format", "output", | |||||
type=click.Choice(["plain", "table", "json", "legacy"]), help="Output format", default="legacy") | |||||
def get_version(output): | |||||
"""Show the versions of all the installed apps.""" | |||||
from git import Repo | |||||
from frappe.utils.commands import render_table | |||||
from frappe.utils.change_log import get_app_branch | from frappe.utils.change_log import get_app_branch | ||||
frappe.init('') | |||||
for m in sorted(frappe.get_all_apps()): | |||||
branch_name = get_app_branch(m) | |||||
module = frappe.get_module(m) | |||||
app_hooks = frappe.get_module(m + ".hooks") | |||||
if hasattr(app_hooks, '{0}_version'.format(branch_name)): | |||||
print("{0} {1}".format(m, getattr(app_hooks, '{0}_version'.format(branch_name)))) | |||||
elif hasattr(module, "__version__"): | |||||
print("{0} {1}".format(m, module.__version__)) | |||||
frappe.init("") | |||||
data = [] | |||||
for app in sorted(frappe.get_all_apps()): | |||||
module = frappe.get_module(app) | |||||
app_hooks = frappe.get_module(app + ".hooks") | |||||
repo = Repo(frappe.get_app_path(app, "..")) | |||||
app_info = frappe._dict() | |||||
app_info.app = app | |||||
app_info.branch = get_app_branch(app) | |||||
app_info.commit = repo.head.object.hexsha[:7] | |||||
app_info.version = getattr(app_hooks, f"{app_info.branch}_version", None) or module.__version__ | |||||
data.append(app_info) | |||||
{ | |||||
"legacy": lambda: [ | |||||
click.echo(f"{app_info.app} {app_info.version}") | |||||
for app_info in data | |||||
], | |||||
"plain": lambda: [ | |||||
click.echo(f"{app_info.app} {app_info.version} {app_info.branch} ({app_info.commit})") | |||||
for app_info in data | |||||
], | |||||
"table": lambda: render_table( | |||||
[["App", "Version", "Branch", "Commit"]] + | |||||
[ | |||||
[app_info.app, app_info.version, app_info.branch, app_info.commit] | |||||
for app_info in data | |||||
] | |||||
), | |||||
"json": lambda: click.echo(json.dumps(data, indent=4)), | |||||
}[output]() | |||||
@click.command('rebuild-global-search') | @click.command('rebuild-global-search') | ||||
@@ -39,18 +39,17 @@ def get_modules_from_app(app): | |||||
) | ) | ||||
def get_all_empty_tables_by_module(): | def get_all_empty_tables_by_module(): | ||||
empty_tables = set(r[0] for r in frappe.db.multisql({ | |||||
"mariadb": """ | |||||
SELECT table_name | |||||
FROM information_schema.tables | |||||
WHERE table_rows = 0 and table_schema = "{}" | |||||
""".format(frappe.conf.db_name), | |||||
"postgres": """ | |||||
SELECT "relname" as "table_name" | |||||
FROM "pg_stat_all_tables" | |||||
WHERE n_tup_ins = 0 | |||||
""" | |||||
})) | |||||
table_rows = frappe.qb.Field("table_rows") | |||||
table_name = frappe.qb.Field("table_name") | |||||
information_schema = frappe.qb.Schema("information_schema") | |||||
empty_tables = ( | |||||
frappe.qb.from_(information_schema.tables) | |||||
.select(table_name) | |||||
.where(table_rows == 0) | |||||
).run() | |||||
empty_tables = {r[0] for r in empty_tables} | |||||
results = frappe.get_all("DocType", fields=["name", "module"]) | results = frappe.get_all("DocType", fields=["name", "module"]) | ||||
empty_tables_by_module = {} | empty_tables_by_module = {} | ||||
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# License: GNU General Public License v3. See license.txt | |||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
@@ -153,7 +153,7 @@ def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, fil | |||||
doctypes = frappe.db.get_all("DocField", filters=filters, fields=["parent"], | doctypes = frappe.db.get_all("DocField", filters=filters, fields=["parent"], | ||||
distinct=True, as_list=True) | distinct=True, as_list=True) | ||||
doctypes = tuple([d for d in doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE)]) | |||||
doctypes = tuple(d for d in doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE)) | |||||
filters.update({ | filters.update({ | ||||
"dt": ("not in", [d[0] for d in doctypes]) | "dt": ("not in", [d[0] for d in doctypes]) | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2015, Frappe Technologies and contributors | # Copyright (c) 2015, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
@@ -257,7 +257,7 @@ def address_query(doctype, txt, searchfield, start, page_len, filters): | |||||
def get_condensed_address(doc): | def get_condensed_address(doc): | ||||
fields = ["address_title", "address_line1", "address_line2", "city", "county", "state", "country"] | fields = ["address_title", "address_line1", "address_line2", "city", "county", "state", "country"] | ||||
return ", ".join([doc.get(d) for d in fields if doc.get(d)]) | |||||
return ", ".join(doc.get(d) for d in fields if doc.get(d)) | |||||
def update_preferred_address(address, field): | def update_preferred_address(address, field): | ||||
frappe.db.set_value('Address', address, field, 0) | frappe.db.set_value('Address', address, field, 0) |
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2015, Frappe Technologies and Contributors | # Copyright (c) 2015, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe, unittest | import frappe, unittest | ||||
from frappe.contacts.doctype.address.address import get_address_display | from frappe.contacts.doctype.address.address import get_address_display | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2015, Frappe Technologies and contributors | # Copyright (c) 2015, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2015, Frappe Technologies and Contributors | # Copyright (c) 2015, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe, unittest | import frappe, unittest | ||||
class TestAddressTemplate(unittest.TestCase): | class TestAddressTemplate(unittest.TestCase): | ||||
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# License: GNU General Public License v3. See license.txt | |||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe.utils import cstr, has_gravatar | from frappe.utils import cstr, has_gravatar | ||||
from frappe import _ | from frappe import _ | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2017, Frappe Technologies and Contributors | # Copyright (c) 2017, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
import unittest | import unittest | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and contributors | # Copyright (c) 2019, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
# import frappe | # import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and contributors | # Copyright (c) 2019, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
# import frappe | # import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2017, Frappe Technologies and contributors | # Copyright (c) 2017, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2017, Frappe Technologies and Contributors | # Copyright (c) 2017, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
import unittest | import unittest | ||||
class TestGender(unittest.TestCase): | class TestGender(unittest.TestCase): | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2017, Frappe Technologies and contributors | # Copyright (c) 2017, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2017, Frappe Technologies and Contributors | # Copyright (c) 2017, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
import unittest | import unittest | ||||
class TestSalutation(unittest.TestCase): | class TestSalutation(unittest.TestCase): | ||||
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors | # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe import _ | from frappe import _ | ||||
@@ -1,2 +1,2 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE |
@@ -1,3 +1,3 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE | |||||
@@ -1,9 +1,5 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# Copyright (c) 2019, Frappe Technologies and contributors | # Copyright (c) 2019, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# imports - standard imports | |||||
# imports - module imports | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -33,4 +29,5 @@ def make_access_log(doctype=None, document=None, method=None, file_type=None, | |||||
doc.insert(ignore_permissions=True) | doc.insert(ignore_permissions=True) | ||||
# `frappe.db.commit` added because insert doesnt `commit` when called in GET requests like `printview` | # `frappe.db.commit` added because insert doesnt `commit` when called in GET requests like `printview` | ||||
frappe.db.commit() | |||||
if frappe.request and frappe.request.method == 'GET': | |||||
frappe.db.commit() |
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and Contributors | # Copyright (c) 2019, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
# imports - standard imports | # imports - standard imports | ||||
import unittest | import unittest | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2017, Frappe Technologies and contributors | # Copyright (c) 2017, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
from frappe import _ | from frappe import _ | ||||
from frappe.utils import get_fullname, now | from frappe.utils import get_fullname, now | ||||
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# License: See license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
import frappe.permissions | import frappe.permissions | ||||
@@ -29,10 +29,12 @@ def update_feed(doc, method=None): | |||||
name = feed.name or doc.name | name = feed.name or doc.name | ||||
# delete earlier feed | # delete earlier feed | ||||
frappe.db.sql("""delete from `tabActivity Log` | |||||
where | |||||
reference_doctype=%s and reference_name=%s | |||||
and link_doctype=%s""", (doctype, name,feed.link_doctype)) | |||||
frappe.db.delete("Activity Log", { | |||||
"reference_doctype": doctype, | |||||
"reference_name": name, | |||||
"link_doctype": feed.link_doctype | |||||
}) | |||||
frappe.get_doc({ | frappe.get_doc({ | ||||
"doctype": "Activity Log", | "doctype": "Activity Log", | ||||
"reference_doctype": doctype, | "reference_doctype": doctype, | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2015, Frappe Technologies and Contributors | # Copyright (c) 2015, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
import unittest | import unittest | ||||
import time | import time | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and contributors | # Copyright (c) 2019, Frappe Technologies and contributors | ||||
# For license information, please see license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe | import frappe | ||||
from frappe import _ | from frappe import _ | ||||
import json | import json | ||||
@@ -9,7 +9,7 @@ from frappe.core.doctype.user.user import extract_mentions | |||||
from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\ | from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\ | ||||
get_title, get_title_html | get_title, get_title_html | ||||
from frappe.utils import get_fullname | from frappe.utils import get_fullname | ||||
from frappe.website.render import clear_cache | |||||
from frappe.website.utils import clear_cache | |||||
from frappe.database.schema import add_column | from frappe.database.schema import add_column | ||||
from frappe.exceptions import ImplicitCommitError | from frappe.exceptions import ImplicitCommitError | ||||
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Copyright (c) 2019, Frappe Technologies and Contributors | # Copyright (c) 2019, Frappe Technologies and Contributors | ||||
# See license.txt | |||||
# License: MIT. See LICENSE | |||||
import frappe, json | import frappe, json | ||||
import unittest | import unittest | ||||
@@ -30,7 +30,7 @@ class TestComment(unittest.TestCase): | |||||
from frappe.website.doctype.blog_post.test_blog_post import make_test_blog | from frappe.website.doctype.blog_post.test_blog_post import make_test_blog | ||||
test_blog = make_test_blog() | test_blog = make_test_blog() | ||||
frappe.db.sql("delete from `tabComment` where reference_doctype = 'Blog Post'") | |||||
frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) | |||||
from frappe.templates.includes.comments.comments import add_comment | from frappe.templates.includes.comments.comments import add_comment | ||||
add_comment('Good comment with 10 chars', 'test@test.com', 'Good Tester', | add_comment('Good comment with 10 chars', 'test@test.com', 'Good Tester', | ||||
@@ -41,7 +41,7 @@ class TestComment(unittest.TestCase): | |||||
reference_name = test_blog.name | reference_name = test_blog.name | ||||
))[0].published, 1) | ))[0].published, 1) | ||||
frappe.db.sql("delete from `tabComment` where reference_doctype = 'Blog Post'") | |||||
frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) | |||||
add_comment('pleez vizits my site http://mysite.com', 'test@test.com', 'bad commentor', | add_comment('pleez vizits my site http://mysite.com', 'test@test.com', 'bad commentor', | ||||
'Blog Post', test_blog.name, test_blog.route) | 'Blog Post', test_blog.name, test_blog.route) | ||||
@@ -1,3 +1,3 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# MIT License. See license.txt | |||||
# License: MIT. See LICENSE | |||||