From c2e44754d17b0056152b24fd09a67d3bb17a431e Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 1 Sep 2022 18:43:05 +0530 Subject: [PATCH 01/84] feat: allow syncing new fields in Doctype Layout --- .../doctype/custom_field/custom_field.py | 14 +++ .../doctype/doctype_layout/doctype_layout.js | 89 +++++++++++++++---- .../doctype_layout/doctype_layout.json | 9 +- .../doctype/doctype_layout/doctype_layout.py | 59 ++++++++++++ 4 files changed, 152 insertions(+), 19 deletions(-) diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index f50ceb1992..7b55b4bc6b 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -102,6 +102,20 @@ class CustomField(Document): # delete property setter entries frappe.db.delete("Property Setter", {"doc_type": self.dt, "field_name": self.fieldname}) + + # update doctype layouts + doctype_layouts = frappe.get_all( + "DocType Layout", filters={"document_type": self.dt}, pluck="name" + ) + + for layout in doctype_layouts: + layout_doc = frappe.get_doc("DocType Layout", layout) + for field in layout_doc.fields: + if field.fieldname == self.fieldname: + layout_doc.remove(field) + layout_doc.save() + break + frappe.clear_cache(doctype=self.dt) def validate_insert_after(self, meta): diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.js b/frappe/custom/doctype/doctype_layout/doctype_layout.js index f91f04f762..45c2de8447 100644 --- a/frappe/custom/doctype/doctype_layout/doctype_layout.js +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.js @@ -2,31 +2,88 @@ // For license information, please see license.txt frappe.ui.form.on("DocType Layout", { - refresh: function (frm) { - frm.trigger("document_type"); - frm.events.set_button(frm); + onload_post_render(frm) { + // disallow users from manually adding/deleting rows; this doctype should only + // be used for managing layout, and docfields and custom fields should be used + // to manage other field metadata (hidden, etc.) + frm.set_df_property("fields", "cannot_add_rows", true); + frm.set_df_property("fields", "cannot_delete_rows", true); + }, + + refresh(frm) { + frm.events.add_buttons(frm); }, document_type(frm) { - frm.set_fields_as_options("fields", frm.doc.document_type, null, [], "fieldname").then( - () => { - // child table empty? then show all fields as default - if (frm.doc.document_type) { - if (!(frm.doc.fields || []).length) { - for (let f of frappe.get_doc("DocType", frm.doc.document_type).fields) { - frm.add_child("fields", { fieldname: f.fieldname, label: f.label }); - } - } - } - } - ); + if (frm.doc.document_type) { + frm.set_value("fields", []); + frm.events.sync_fields(frm, false); + } }, - set_button(frm) { + add_buttons(frm) { if (!frm.is_new()) { frm.add_custom_button(__("Go to {0} List", [frm.doc.name]), () => { window.open(`/app/${frappe.router.slug(frm.doc.name)}`); }); + + frm.add_custom_button(__("Sync {0} Fields", [frm.doc.name]), async () => { + await frm.events.sync_fields(frm, true); + }); + } + }, + + async sync_fields(frm, notify) { + frappe.dom.freeze("Fetching fields..."); + const response = await frm.call({ doc: frm.doc, method: "sync_fields" }); + frm.refresh_field("fields"); + frappe.dom.unfreeze(); + + if (!response.message) { + frappe.msgprint(__("No changes to sync")); + return; + } + + frm.dirty(); + if (notify) { + const addedFields = response.message.added; + const removedFields = response.message.removed; + + const getChangedMessage = (fields) => { + let changes = ""; + for (const field of fields) { + if (field.label) { + changes += `
  • Row #${field.idx}: ${field.fieldname.bold()} (${ + field.label + })
  • `; + } else { + changes += `
  • Row #${field.idx}: ${field.fieldname.bold()}
  • `; + } + } + return changes; + }; + + let message = ""; + + if (addedFields.length) { + message += `The following fields have been added:

    `; + } + + if (removedFields.length) { + message += `The following fields have been removed:

    `; + } + + if (message) { + frappe.msgprint({ + message: __(message), + indicator: "green", + title: __("Synced Fields"), + }); + } } }, }); diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.json b/frappe/custom/doctype/doctype_layout/doctype_layout.json index e47c9e03e0..0b627f78ce 100644 --- a/frappe/custom/doctype/doctype_layout/doctype_layout.json +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.json @@ -1,7 +1,7 @@ { "actions": [], "allow_rename": 1, - "autoname": "Prompt", + "autoname": "prompt", "creation": "2020-11-16 17:05:35.306846", "doctype": "DocType", "editable_grid": 1, @@ -19,7 +19,8 @@ "in_list_view": 1, "label": "Document Type", "options": "DocType", - "reqd": 1 + "reqd": 1, + "set_only_once": 1 }, { "fieldname": "fields", @@ -42,10 +43,11 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-10 15:01:04.352184", + "modified": "2022-09-01 03:22:33.973058", "modified_by": "Administrator", "module": "Custom", "name": "DocType Layout", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { @@ -68,5 +70,6 @@ "route": "doctype-layout", "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.py b/frappe/custom/doctype/doctype_layout/doctype_layout.py index ea8e9acc99..778f2aa024 100644 --- a/frappe/custom/doctype/doctype_layout/doctype_layout.py +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.py @@ -1,11 +1,70 @@ # Copyright (c) 2020, Frappe Technologies and contributors # License: MIT. See LICENSE +from typing import TYPE_CHECKING + +import frappe from frappe.desk.utils import slug from frappe.model.document import Document +if TYPE_CHECKING: + from frappe.core.doctype.docfield.docfield import DocField + class DocTypeLayout(Document): def validate(self): if not self.route: self.route = slug(self.name) + + @frappe.whitelist() + def sync_fields(self): + layout_fieldnames = {field.fieldname for field in self.fields} + doctype_fields = frappe.get_meta(self.document_type).fields + doctype_fieldnames = {field.fieldname for field in doctype_fields} + + added_fields = list(doctype_fieldnames - layout_fieldnames) + removed_fields = list(layout_fieldnames - doctype_fieldnames) + + if not (added_fields or removed_fields): + return + + added = self.add_fields(added_fields, doctype_fields) + removed = self.remove_fields(removed_fields) + + for index, field in enumerate(self.fields): + field.idx = index + 1 + + return {"added": added, "removed": removed} + + def add_fields(self, added_fields: list[str], doctype_fields: list["DocField"]) -> list[dict]: + added = [] + for field in added_fields: + field_details = next((f for f in doctype_fields if f.fieldname == field), None) + if not field_details: + continue + + # remove 'doctype' data from the DocField to allow adding it to the layout + row = self.append("fields", field_details.as_dict(no_default_fields=True)) + if field_details.insert_after: + insert_after = next( + (f for f in self.fields if f.fieldname == field_details.insert_after), + None, + ) + + # initialize new row to just after the insert_after field + self.fields.insert(insert_after.idx, row) + self.fields.pop() + + added.append({"idx": insert_after.idx + 1, "fieldname": row.fieldname, "label": row.label}) + else: + added.append(row.as_dict()) + return added + + def remove_fields(self, removed_fields: list[str]) -> list[dict]: + removed = [] + for field in removed_fields: + field_details = next((f for f in self.fields if f.fieldname == field), None) + if field_details: + self.remove(field_details) + removed.append(field_details.as_dict()) + return removed From 44e9f6b9abff67186ba3d4ca8bb1e508065af58f Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 28 Sep 2022 14:55:07 +0530 Subject: [PATCH 02/84] build(deps): Bump from vue 2 to vue 3 --- esbuild/esbuild.js | 27 ++++++++++++++++----- esbuild/frappe-vue-style.js | 48 +++++++++++++++++++++++++++++++++++++ package.json | 10 ++++---- 3 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 esbuild/frappe-vue-style.js diff --git a/esbuild/esbuild.js b/esbuild/esbuild.js index 69e479a6ff..ad7e8e741a 100644 --- a/esbuild/esbuild.js +++ b/esbuild/esbuild.js @@ -3,11 +3,12 @@ const path = require("path"); const fs = require("fs"); const glob = require("fast-glob"); const esbuild = require("esbuild"); -const vue = require("esbuild-vue"); +const vue = require("esbuild-plugin-vue3"); const yargs = require("yargs"); const cliui = require("cliui")(); const chalk = require("chalk"); const html_plugin = require("./frappe-html"); +const vue_style_plugin = require("./frappe-vue-style"); const rtlcss = require("rtlcss"); const postCssPlugin = require("@frappe/esbuild-plugin-postcss2").default; const ignore_assets = require("./ignore-assets"); @@ -155,7 +156,7 @@ function build_assets_for_apps(apps, files) { style_file_map[output_name] = file; rtl_style_file_map[output_name.replace("/css/", "/css-rtl/")] = file; } else { - file_map[output_name] = file; + file_map[output_name.replace("/js/", "/")] = file; } } let build = build_files({ @@ -218,8 +219,9 @@ function get_files_to_build(files) { } function build_files({ files, outdir }) { - let build_plugins = [html_plugin, build_cleanup_plugin, vue()]; - return esbuild.build(get_build_options(files, outdir, build_plugins)); + let build_plugins = [vue(), html_plugin, build_cleanup_plugin, vue_style_plugin]; + let entry_names = "[dir]/[ext]/[name].[hash]"; + return esbuild.build(get_build_options(files, outdir, build_plugins, entry_names)); } function build_style_files({ files, outdir, rtl_style = false }) { @@ -241,10 +243,11 @@ function build_style_files({ files, outdir, rtl_style = false }) { return esbuild.build(get_build_options(files, outdir, build_plugins)); } -function get_build_options(files, outdir, plugins) { +function get_build_options(files, outdir, plugins, entry_names) { + let entryNames = entry_names || "[dir]/[name].[hash]"; return { entryPoints: files, - entryNames: "[dir]/[name].[hash]", + entryNames, target: ["es2017"], outdir, sourcemap: true, @@ -254,6 +257,8 @@ function get_build_options(files, outdir, plugins) { nodePaths: NODE_PATHS, define: { "process.env.NODE_ENV": JSON.stringify(PRODUCTION ? "production" : "development"), + "__VUE_OPTIONS_API__": JSON.stringify(true), + "__VUE_PROD_DEVTOOLS__": JSON.stringify(false), }, plugins: plugins, watch: get_watch_config(), @@ -378,6 +383,16 @@ async function write_assets_json(metafile) { key = `rtl_${key}`; } out[key] = asset_path; + } else if (Object.keys(info.inputs).length !== 0) { + for (let input in info.inputs) { + if (input.includes(".vue?type=style")) { + // remove hash from css file name + let key = path.basename(asset_path); + key = key.split('.css')[0]; + key = key.substring(0, key.lastIndexOf(".")) + '.css'; + out[key] = asset_path; + } + } } } diff --git a/esbuild/frappe-vue-style.js b/esbuild/frappe-vue-style.js new file mode 100644 index 0000000000..8147aec777 --- /dev/null +++ b/esbuild/frappe-vue-style.js @@ -0,0 +1,48 @@ +const fs = require("fs"); +const path = require("path"); +const { sites_path } = require("./utils"); + +module.exports = { + name: "frappe-vue-style", + setup(build) { + build.initialOptions.write = false; + build.onEnd((result) => { + let files = get_files(result.metafile.outputs); + let keys = Object.keys(files); + for (let out of result.outputFiles) { + let asset_path = "/" + path.relative(sites_path, out.path); + let dir = path.dirname(out.path); + if (out.path.endsWith(".js") && keys.includes(asset_path)) { + let bundle_css = files[asset_path]; + let include_css = '\nfrappe.require("' + bundle_css + '");\n'; + let modified = include_css + out.text; + out.contents = Buffer.from(modified); + } + if (!fs.existsSync(dir)){ + fs.mkdirSync(dir); + } + fs.writeFile(out.path, out.contents, (err) => { + err && console.error(err); + }); + } + }); + }, +}; + +function get_files(files) { + let result = {}; + for (let file in files) { + let info = files[file]; + let asset_path = "/" + path.relative(sites_path, file); + if (info && info.entryPoint && Object.keys(info.inputs).length !== 0) { + for (let input in info.inputs) { + if (input.includes(".vue?type=style")) { + let bundle_css = path.basename(info.entryPoint).replace(".js", ".css"); + result[asset_path] = bundle_css; + break; + } + } + } + } + return result; +} diff --git a/package.json b/package.json index 18063c69cf..b1252a1b83 100644 --- a/package.json +++ b/package.json @@ -58,23 +58,23 @@ "sortablejs": "1.9.0", "superagent": "^3.8.2", "touch": "^3.1.0", - "vue": "2.6.14", - "vue-router": "^2.0.0", + "vue": "3.2.39", + "vue-router": "^4.1.5", "vuedraggable": "^2.24.3", - "vuex": "3", + "vuex": "4.0.2", "@frappe/esbuild-plugin-postcss2": "^0.1.3", "@vue/component-compiler": "^4.2.4", "autoprefixer": "10", "chalk": "^2.3.2", "cliui": "^7.0.4", "esbuild": "^0.14.29", - "esbuild-vue": "^1.2.1", + "esbuild-plugin-vue3": "^0.3.0", "fast-glob": "^3.2.5", "launch-editor": "^2.2.1", "md5": "^2.3.0", "postcss": "8", "rtlcss": "^3.2.1", - "vue-template-compiler": "2.6.14", + "@vue/compiler-sfc": "^3.2.39", "yargs": "^17.5.1" }, "snyk": true, From 2c9a447f838fb5166401ba6e2ee8a4d074a8f72f Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 28 Sep 2022 15:30:59 +0530 Subject: [PATCH 03/84] chore: linter fixes --- esbuild/esbuild.js | 8 ++++---- esbuild/frappe-vue-style.js | 2 +- frappe/public/js/frappe/widgets/quick_list_widget.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esbuild/esbuild.js b/esbuild/esbuild.js index ad7e8e741a..afda75ebb0 100644 --- a/esbuild/esbuild.js +++ b/esbuild/esbuild.js @@ -257,8 +257,8 @@ function get_build_options(files, outdir, plugins, entry_names) { nodePaths: NODE_PATHS, define: { "process.env.NODE_ENV": JSON.stringify(PRODUCTION ? "production" : "development"), - "__VUE_OPTIONS_API__": JSON.stringify(true), - "__VUE_PROD_DEVTOOLS__": JSON.stringify(false), + __VUE_OPTIONS_API__: JSON.stringify(true), + __VUE_PROD_DEVTOOLS__: JSON.stringify(false), }, plugins: plugins, watch: get_watch_config(), @@ -388,8 +388,8 @@ async function write_assets_json(metafile) { if (input.includes(".vue?type=style")) { // remove hash from css file name let key = path.basename(asset_path); - key = key.split('.css')[0]; - key = key.substring(0, key.lastIndexOf(".")) + '.css'; + key = key.split(".css")[0]; + key = key.substring(0, key.lastIndexOf(".")) + ".css"; out[key] = asset_path; } } diff --git a/esbuild/frappe-vue-style.js b/esbuild/frappe-vue-style.js index 8147aec777..d8b4711e30 100644 --- a/esbuild/frappe-vue-style.js +++ b/esbuild/frappe-vue-style.js @@ -18,7 +18,7 @@ module.exports = { let modified = include_css + out.text; out.contents = Buffer.from(modified); } - if (!fs.existsSync(dir)){ + if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } fs.writeFile(out.path, out.contents, (err) => { diff --git a/frappe/public/js/frappe/widgets/quick_list_widget.js b/frappe/public/js/frappe/widgets/quick_list_widget.js index 4fefbf4271..dbb26cb62c 100644 --- a/frappe/public/js/frappe/widgets/quick_list_widget.js +++ b/frappe/public/js/frappe/widgets/quick_list_widget.js @@ -26,7 +26,7 @@ export default class QuickListWidget extends Widget { setup_add_new_button() { this.add_new_button = $( - `
    ${frappe.utils.icon("add", "sm")} From 6bd95516bad0a0403183fecbc676edc942b6caaa Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 28 Sep 2022 15:35:28 +0530 Subject: [PATCH 04/84] chore: removed compiler-sfc from package.json since it comes with vue --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index b1252a1b83..15e8e9bbc7 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "md5": "^2.3.0", "postcss": "8", "rtlcss": "^3.2.1", - "@vue/compiler-sfc": "^3.2.39", "yargs": "^17.5.1" }, "snyk": true, From 60d9335d383dc6b5c38bab4524856c62b90f2c33 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 29 Sep 2022 14:24:34 +0530 Subject: [PATCH 05/84] refactor: Recorder code to vue3 --- frappe/core/page/recorder/recorder.js | 4 +- .../js/frappe/recorder/RecorderDetail.vue | 385 ++++++++++-------- .../js/frappe/recorder/RecorderRoot.vue | 29 +- .../js/frappe/recorder/RequestDetail.vue | 250 ++++++------ .../js/frappe/recorder/recorder.bundle.js | 5 + frappe/public/js/frappe/recorder/recorder.js | 48 --- frappe/public/js/frappe/recorder/router.js | 28 ++ frappe/public/js/frappe/recorder/utils.js | 3 + frappe/public/js/recorder.bundle.js | 1 - 9 files changed, 384 insertions(+), 369 deletions(-) create mode 100644 frappe/public/js/frappe/recorder/recorder.bundle.js delete mode 100644 frappe/public/js/frappe/recorder/recorder.js create mode 100644 frappe/public/js/frappe/recorder/router.js create mode 100644 frappe/public/js/frappe/recorder/utils.js delete mode 100644 frappe/public/js/recorder.bundle.js diff --git a/frappe/core/page/recorder/recorder.js b/frappe/core/page/recorder/recorder.js index 83b8d1a636..1f004915fe 100644 --- a/frappe/core/page/recorder/recorder.js +++ b/frappe/core/page/recorder/recorder.js @@ -22,7 +22,7 @@ class Recorder { } show() { - if (!this.view || this.view.$route.name == "recorder-detail") return; - this.view.$router.replace({ name: "recorder-detail" }); + if (!this.route || this.route.name == "RecorderDetail") return; + this.router?.replace({ name: "RecorderDetail" }); } } diff --git a/frappe/public/js/frappe/recorder/RecorderDetail.vue b/frappe/public/js/frappe/recorder/RecorderDetail.vue index 0b6354db7e..001eb0a0bf 100644 --- a/frappe/public/js/frappe/recorder/RecorderDetail.vue +++ b/frappe/public/js/frappe/recorder/RecorderDetail.vue @@ -5,15 +5,24 @@
    -
    -

    -

    {{ __("Recorder is Inactive.") }}

    -

    {{ __("Start recording or drag & drop a previously exported data file to view it.") }}

    +

    + +

    +

    {{ translated_string("Recorder is Inactive.") }}

    +

    {{ translated_string("Start recording or drag & drop a previously exported data file to view it.") }}

    -

    {{ __("No Requests found") }}

    -

    {{ __("Go make some noise") }}

    +

    {{ translated_string("No Requests found") }}

    +

    {{ translated_string("Go make some noise") }}

    @@ -102,181 +115,193 @@
    - - diff --git a/frappe/public/js/frappe/recorder/RecorderRoot.vue b/frappe/public/js/frappe/recorder/RecorderRoot.vue index 479ab1b2ca..60c5c59453 100644 --- a/frappe/public/js/frappe/recorder/RecorderRoot.vue +++ b/frappe/public/js/frappe/recorder/RecorderRoot.vue @@ -1,17 +1,20 @@ - diff --git a/frappe/public/js/frappe/recorder/RequestDetail.vue b/frappe/public/js/frappe/recorder/RequestDetail.vue index 8ee6ff631b..a77766e8de 100644 --- a/frappe/public/js/frappe/recorder/RequestDetail.vue +++ b/frappe/public/js/frappe/recorder/RequestDetail.vue @@ -16,7 +16,7 @@
    -
    {{ __("SQL Queries") }}
    +
    {{ translated_string("SQL Queries") }}
    @@ -37,7 +37,7 @@
    @@ -48,15 +48,15 @@
    - {{ __("Index") }}
    + {{ translated_string("Index") }}
    -
    {{ __("Query") }}
    +
    {{ translated_string("Query") }}
    -
    {{ __("Duration (ms)") }}
    +
    {{ translated_string("Duration (ms)") }}"
    -
    {{ __("Exact Copies") }}
    +
    {{ translated_string("Exact Copies") }}
    @@ -82,7 +82,7 @@
    - {{ __("SQL Query") }} #{{ call.index }} + {{ translated_string("SQL Query") }} #{{ call.index }}
    @@ -95,25 +95,25 @@
    -
    +
    -
    +
    -
    +
    -
    +
    @@ -134,7 +132,7 @@
    -
    +