diff --git a/frappe/boot.py b/frappe/boot.py index 2af5107988..dc8c99199e 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -528,16 +528,25 @@ def get_sentry_dsn(): def get_sidebar_items(): + from frappe.utils.install import auto_generate_sidebar_from_module + sidebars = frappe.get_all( "Workspace Sidebar", fields=["name", "header_icon"], filters={"name": ["not like", "%My Workspaces%"]} ) + module_sidebars = auto_generate_sidebar_from_module() + sidebars.extend(module_sidebars) add_user_specific_sidebar(sidebars) sidebar_items = {} for s in sidebars: - w = frappe.get_doc("Workspace Sidebar", s.get("name")) - sidebar_items[s["name"].lower()] = { - "label": s.get("name"), + sidebar_title = s.get("name") + if sidebar_title: + w = frappe.get_doc("Workspace Sidebar", sidebar_title) + else: + sidebar_title = s.title + w = s + sidebar_items[sidebar_title.lower()] = { + "label": sidebar_title, "items": [], "header_icon": s.get("header_icon"), "module": w.module, @@ -569,11 +578,11 @@ def get_sidebar_items(): "ref_doctype": ref_doctype, } if ( - "My Workspaces" in s["name"] + "My Workspaces" in sidebar_title or si.type == "Section Break" or w.is_item_allowed(si.link_to, si.link_type) ): - sidebar_items[s.get("name").lower()]["items"].append(workspace_sidebar) + sidebar_items[sidebar_title.lower()]["items"].append(workspace_sidebar) old_name = f"my workspaces-{frappe.session.user.lower()}" if old_name in sidebar_items.keys(): diff --git a/frappe/patches.txt b/frappe/patches.txt index 93ff293ccb..5b076fab59 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -252,4 +252,3 @@ frappe.patches.v16_0.auto_generate_desktop_icon_and_sidebar frappe.patches.v16_0.add_private_workspaces_to_sidebar frappe.core.doctype.communication_link.patches.copy_communication_date_to_link frappe.core.doctype.communication.patches.drop_ref_dt_dn_index -frappe.patches.v16_0.auto_generate_sidebar_from_modules diff --git a/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py b/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py deleted file mode 100644 index 3f78ffeb86..0000000000 --- a/frappe/patches/v16_0/auto_generate_sidebar_from_modules.py +++ /dev/null @@ -1,93 +0,0 @@ -import frappe - - -def execute(): - """Auto generate sidebar from module""" - sidebars = [] - for module in frappe.get_all("Module Def", pluck="name"): - if not ( - frappe.db.exists("Workspace Sidebar", {"module": module}) - or frappe.db.exists("Workspace Sidebar", {"name": module}) - ): - print("Fetching information for Module", module) - module_info = get_module_info(module) - sidebar_items = create_sidebar_items(module_info) - sidebar = frappe.new_doc("Workspace Sidebar") - sidebar.title = module - sidebar.items = sidebar_items - sidebar.header_icon = "hammer" - sidebars.append(sidebar) - sidebar.save() - - -def get_module_info(module_name): - entities = ["Workspace", "Dashboard", "DocType", "Report", "Page"] - module_info = {} - - for entity in entities: - module_info[entity] = {} - filters = [{"module": module_name}] - if entity.lower() == "doctype": - filters.append({"istable": 0}) - module_info[entity] = frappe.get_all(entity, filters=filters, pluck="name") - - # if module info has no workspaces, then move doctypes to the front - if not module_info.get("Workspace"): - module_info = { - "DocType": module_info.get("DocType"), - "Workspace": module_info.get("Workspace"), - "Report": module_info.get("Report"), - "Dashboard": module_info.get("Dashboard"), - "Page": module_info.get("Page"), - } - return module_info - - -def create_sidebar_items(module_info): - sidebar_items = [] - idx = 1 - - section_entities = {"report": "Reports", "dashboard": "Dashboards", "page": "Pages"} - - for entity, items in module_info.items(): - section_break_added = False - entity_lower = entity.lower() - - if entity_lower in section_entities: - if entity_lower == "report": - section_break = add_section_breaks("Reports", idx) - elif entity_lower in ("dashboard", "page") and len(items) > 1: - section_break = add_section_breaks(section_entities[entity_lower], idx) - section_break_added = True - sidebar_items.append(section_break) - idx += 1 - - for item in items: - print(entity, item) - item_info = {"label": item, "type": "Link", "link_type": entity, "link_to": item, "idx": idx} - - if entity_lower == "report": - item_info["child"] = 1 - - if entity_lower == "workspace": - item_info["icon"] = "home" - - if entity_lower == "doctype" and "settings" in item.lower(): - item_info["icon"] = "settings" - - if section_break_added: - item_info["child"] = 1 - - sidebar_item = frappe.new_doc("Workspace Sidebar Item") - sidebar_item.update(item_info) - sidebar_items.append(sidebar_item) - - idx += 1 - - return sidebar_items - - -def add_section_breaks(label, idx): - section_break = frappe.new_doc("Workspace Sidebar Item") - section_break.update({"label": label, "type": "Section Break", "idx": idx}) - return section_break diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar.js b/frappe/public/js/frappe/ui/sidebar/sidebar.js index 5d7106de6f..e4e3810216 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar.js @@ -445,7 +445,7 @@ frappe.ui.Sidebar = class Sidebar { if (route.length == 2) { workspace_title = this.get_correct_workspace_sidebars(route[1]); } else { - workspace_title = this.get_correct_workspace_sidebars(route); + workspace_title = this.get_correct_workspace_sidebars(route[0]); } let module_name = workspace_title[0]; if (module_name) { diff --git a/frappe/utils/install.py b/frappe/utils/install.py index 60a393882e..2f885370c8 100644 --- a/frappe/utils/install.py +++ b/frappe/utils/install.py @@ -5,6 +5,7 @@ import getpass import frappe from frappe.geo.doctype.country.country import import_country_and_currency from frappe.utils import cint +from frappe.utils.caching import site_cache from frappe.utils.password import update_password @@ -221,3 +222,135 @@ def delete_desktop_icon_and_sidebar(app_name, dry_run=False): if dry_run: # Delete icons and sidebars frappe.db.commit() # nosemgrep + + +@site_cache() +def auto_generate_sidebar_from_module(): + """Auto generate sidebar from module""" + sidebars = [] + for module in frappe.get_all("Module Def", pluck="name"): + if not ( + frappe.db.exists("Workspace Sidebar", {"module": module}) + or frappe.db.exists("Workspace Sidebar", {"name": module}) + ): + module_info = get_module_info(module) + sidebar_items = create_sidebar_items(module_info) + sidebar = frappe.new_doc("Workspace Sidebar") + sidebar.title = module + sidebar.items = sidebar_items + sidebar.module = module + sidebar.header_icon = "hammer" + sidebar.app = frappe.local.module_app.get(frappe.scrub(module), None) + sidebars.append(sidebar) + return sidebars + + +def get_module_info(module_name): + entities = ["Workspace", "Dashboard", "DocType", "Report", "Page"] + module_info = {} + + for entity in entities: + module_info[entity] = {} + filters = [{"module": module_name}] + pluck = "name" + fieldnames = ["name"] + if entity.lower() == "doctype": + filters.append({"istable": 0}) + if entity.lower() == "page": + fieldnames.append("title") + pluck = None + module_info[entity] = frappe.get_all( + entity, filters=filters, fields=fieldnames, pluck=pluck, order_by="creation asc" + ) + + # if module info has no workspaces, then move doctypes to the front + if not module_info.get("Workspace"): + module_info = { + "DocType": module_info.get("DocType"), + "Workspace": module_info.get("Workspace"), + "Report": module_info.get("Report"), + "Dashboard": module_info.get("Dashboard"), + "Page": module_info.get("Page"), + } + top_doctypes = choose_top_doctypes(module_info.get("DocType")) + if top_doctypes: + module_info["DocType"] = choose_top_doctypes(module_info.get("DocType")) + return module_info + + +def choose_top_doctypes(doctype_names): + doctype_limit = 3 + if len(doctype_names) > doctype_limit: + try: + doctype_count_map = {} + for doctype in doctype_names: + doctype_count_map[doctype] = frappe.db.count(doctype) + top_doctypes = [ + name + for name, count in sorted(doctype_count_map.items(), key=lambda x: x[1], reverse=True)[ + :doctype_limit + ] + ] + return top_doctypes + except frappe.db.ProgrammingError: + # catches table not found errors + return None + + +def create_sidebar_items(module_info): + sidebar_items = [] + idx = 1 + + section_entities = {"report": "Reports", "dashboard": "Dashboards", "page": "Pages"} + + for entity, items in module_info.items(): + section_break_added = False + entity_lower = entity.lower() + + if entity_lower in section_entities: + if entity_lower == "report": + section_break = add_section_breaks("Reports", idx) + elif entity_lower in ("dashboard", "page") and len(items) > 1: + section_break = add_section_breaks(section_entities[entity_lower], idx) + section_break_added = True + sidebar_items.append(section_break) + idx += 1 + + for item in items: + print(entity, item) + item_info = {"label": item, "type": "Link", "link_type": entity, "link_to": item, "idx": idx} + + if entity_lower == "report": + item_info["child"] = 1 + item_info["icon"] = "table" + + if entity_lower == "page": + item_info["label"] = item.get("title") + item_info["link_to"] = item.get("name") + + if entity_lower == "workspace": + item_info["icon"] = "home" + item_info["icon"] = "wallpaper" + + if entity_lower == "page": + item_info["icon"] = "panel-top" + + if entity_lower == "doctype" and "settings" in item.lower(): + item_info["icon"] = "settings" + + if section_break_added: + item_info["child"] = 1 + + sidebar_item = frappe.new_doc("Workspace Sidebar Item") + sidebar_item.update(item_info) + sidebar_items.append(sidebar_item) + + idx += 1 + + return sidebar_items + + +def add_section_breaks(label, idx): + section_break = frappe.new_doc("Workspace Sidebar Item") + section_break.update({"label": label, "type": "Section Break", "idx": idx}) + return section_break