From d5ad0ff2e8f993cf081c38e1744324eaef15e852 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 21 Dec 2017 11:55:49 +0530 Subject: [PATCH] Setup stages (#4618) * setup complete stages * [setup] better setup-in-progress card * restructure setup exception flow * use setup_stages hook * Add message for non-dev mode, fail instead of error * message to not include commits in app setup stages --- frappe/desk/page/setup_wizard/setup_wizard.js | 146 ++++++++++------- frappe/desk/page/setup_wizard/setup_wizard.py | 149 +++++++++++++----- frappe/public/css/page.css | 49 +----- frappe/public/less/page.less | 50 +----- 4 files changed, 207 insertions(+), 187 deletions(-) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index f3045a8ce03..3b75c5cba44 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -182,39 +182,72 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { } action_on_complete() { - var me = this; if (!this.current_slide.set_values()) return; this.update_values(); this.show_working_state(); this.disable_keyboard_nav(); + this.listen_for_setup_stages(); + return frappe.call({ method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete", args: {args: this.values}, - callback: function() { - me.show_setup_complete_state(); - if(frappe.setup.welcome_page) { - localStorage.setItem("session_last_route", frappe.setup.welcome_page); + callback: (r) => { + if(r.message.status === 'ok') { + this.post_setup_success(); + } else if(r.message.fail !== undefined) { + this.abort_setup(r.message.fail); } - setTimeout(function() { - // Reload - window.location.href = ''; - }, 2000); - setTimeout(()=> { - $('body').removeClass('setup-state'); - }, 20000); }, - error: function() { - var d = frappe.msgprint(__("There were errors.")); - d.custom_onhide = function() { - $(me.parent).find('.page-card-container').remove(); - $('body').removeClass('setup-state'); - me.container.show(); - frappe.set_route(me.page_name, me.slides.length - 1); - }; - } + error: this.abort_setup.bind(this, "Error in setup", true) }); } + post_setup_success() { + this.set_setup_complete_message(__("Setup Complete"), __("Refreshing...")); + if(frappe.setup.welcome_page) { + localStorage.setItem("session_last_route", frappe.setup.welcome_page); + } + setTimeout(function() { + // Reload + window.location.href = ''; + }, 2000); + } + + abort_setup(fail_msg, error=false) { + this.$working_state.find('.state-icon-container').html(''); + fail_msg = fail_msg ? fail_msg : __("Failed to complete setup"); + + if(error && !frappe.boot.developer_mode) { + frappe.msgprint(`Don't worry. It's not you, it's us. We've + received the issue details and will get back to you on the solution. + Please feel free to contact us on support@erpnext.com in the meantime.`); + } + + this.update_setup_message('Could not start up: ' + fail_msg); + + this.$working_state.find('.title').html('Setup failed'); + + this.$abort_btn.show(); + } + + listen_for_setup_stages() { + frappe.realtime.on("setup_task", (data) => { + // console.log('data', data); + if(data.stage_status) { + // .html('Process '+ data.progress[0] + ' of ' + data.progress[1] + ': ' + data.stage_status); + this.update_setup_message(data.stage_status); + this.set_setup_load_percent((data.progress[0]+1)/data.progress[1] * 100); + } + if(data.fail_msg) { + this.abort_setup(data.fail_msg); + } + }) + } + + update_setup_message(message) { + this.$working_state.find('.setup-message').html(message); + } + get_setup_slides_filtered_by_domain() { var filtered_slides = []; frappe.setup.slides.forEach(function(slide) { @@ -233,51 +266,56 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { show_working_state() { this.container.hide(); - $('body').addClass('setup-state'); frappe.set_route(this.page_name); - this.working_state_message = this.get_message( - __("Setting Up"), - __("Sit tight while your system is being setup. This may take a few moments."), - true - ).appendTo(this.parent); + this.$working_state = this.get_message( + __("Setting up your system"), + __("Starting Frappé ...")).appendTo(this.parent); + + this.attach_abort_button(); this.current_id = this.slides.length; this.current_slide = null; - this.completed_state_message = this.get_message( - __("Setup Complete"), - __("Refreshing...") - ); } - show_setup_complete_state() { - this.working_state_message.hide(); - this.completed_state_message.appendTo(this.parent); + attach_abort_button() { + this.$abort_btn = $(``); + this.$working_state.find('.content').append(this.$abort_btn); + + this.$abort_btn.on('click', () => { + $(this.parent).find('.setup-in-progress').remove(); + this.container.show(); + frappe.set_route(this.page_name, this.slides.length - 1); + }); + + this.$abort_btn.hide(); } - get_message(title, message="", loading=false) { - const loading_html = loading - ? '
' - : `
- -
`; - - return $(`
-
-
- ${loading - ? `${title}` - : `${title}` - } -
-

${message}

-
- ${loading_html} -
+ get_message(title, message="") { + const loading_html = `
+
+
+
+
`; + + return $(`
+
+

${title}

+
${loading_html}
+

${message}

`); } + + set_setup_complete_message(title, message) { + this.$working_state.find('.title').html(title); + this.$working_state.find('.setup-message').html(message); + } + + set_setup_load_percent(percent) { + this.$working_state.find('.progress-bar').css({"width": percent + "%"}); + } }; frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide { diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index a986d24d45a..eef203ab04d 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -13,50 +13,117 @@ from . import install_fixtures from six import string_types +def get_setup_stages(args): + + # App setup stage functions should not include frappe.db.commit + # That is done by frappe after successful completion of all stages + stages = [ + { + 'status': 'Updating global settings', + 'fail_msg': 'Failed to update global settings', + 'tasks': [ + { + 'fn': update_global_settings, + 'args': args, + 'fail_msg': 'Failed to update global settings' + } + ] + } + ] + + stages += get_stages_hooks(args) + get_setup_complete_hooks(args) + + stages.append({ + # post executing hooks + 'status': 'Wrapping up', + 'fail_msg': 'Failed to complete setup', + 'tasks': [ + { + 'fn': run_post_setup_complete, + 'args': args, + 'fail_msg': 'Failed to complete setup' + } + ] + }) + + return stages + @frappe.whitelist() def setup_complete(args): """Calls hooks for `setup_wizard_complete`, sets home page as `desktop` and clears cache. If wizard breaks, calls `setup_wizard_exception` hook""" + # Setup complete: do not throw an exception, let the user continue to desk if cint(frappe.db.get_single_value('System Settings', 'setup_complete')): - # do not throw an exception if setup is already complete - # let the user continue to desk return - #frappe.throw(_('Setup already complete')) - args = process_args(args) + args = parse_args(args) - try: - if args.language and args.language != "english": - set_default_language(get_language_code(args.language)) + stages = get_setup_stages(args) - frappe.clear_cache() - - # update system settings - update_system_settings(args) - update_user_name(args) - - for method in frappe.get_hooks("setup_wizard_complete"): - frappe.get_attr(method)(args) + try: + current_task = None + for idx, stage in enumerate(stages): + frappe.publish_realtime('setup_task', {"progress": [idx, len(stages)], + "stage_status": stage.get('status')}, user=frappe.session.user) + + for task in stage.get('tasks'): + current_task = task + task.get('fn')(task.get('args')) + + except Exception: + handle_setup_exception(args) + return {'status': 'fail', 'fail': current_task.get('fail_msg')} + else: + run_setup_success(args) + return {'status': 'ok'} - disable_future_access() +def update_global_settings(args): + if args.language and args.language != "english": + set_default_language(get_language_code(args.lang)) + frappe.clear_cache() - frappe.db.commit() - frappe.clear_cache() - except: - frappe.db.rollback() - if args: - traceback = frappe.get_traceback() - for hook in frappe.get_hooks("setup_wizard_exception"): - frappe.get_attr(hook)(traceback, args) + update_system_settings(args) + update_user_name(args) - raise +def run_post_setup_complete(args): + disable_future_access() + frappe.db.commit() + frappe.clear_cache() - else: - for hook in frappe.get_hooks("setup_wizard_success"): - frappe.get_attr(hook)(args) - install_fixtures.install() +def run_setup_success(args): + for hook in frappe.get_hooks("setup_wizard_success"): + frappe.get_attr(hook)(args) + install_fixtures.install() + +def get_stages_hooks(args): + stages = [] + for method in frappe.get_hooks("setup_wizard_stages"): + stages += frappe.get_attr(method)(args) + return stages + +def get_setup_complete_hooks(args): + stages = [] + for method in frappe.get_hooks("setup_wizard_complete"): + stages.append({ + 'status': 'Executing method', + 'fail_msg': 'Failed to execute method', + 'tasks': [ + { + 'fn': frappe.get_attr(method), + 'args': args, + 'fail_msg': 'Failed to execute method' + } + ] + }) + return stages +def handle_setup_exception(args): + frappe.db.rollback() + if args: + traceback = frappe.get_traceback() + for hook in frappe.get_hooks("setup_wizard_exception"): + frappe.get_attr(hook)(traceback, args) def update_system_settings(args): number_format = get_country_info(args.get("country")).get("number_format", "#,###.##") @@ -126,7 +193,7 @@ def update_user_name(args): if args.get('name'): add_all_roles_to(args.get("name")) -def process_args(args): +def parse_args(args): if not args: args = frappe.local.form_dict if isinstance(args, string_types): @@ -234,14 +301,6 @@ def email_setup_wizard_exception(traceback, args): user_agent = frappe._dict() message = """ -#### Basic Information - -- **Site:** {site} -- **User:** {user} -- **Browser:** {user_agent.platform} {user_agent.browser} version: {user_agent.version} language: {user_agent.language} -- **Browser Languages**: `{accept_languages}` - ---- #### Traceback @@ -257,7 +316,16 @@ def email_setup_wizard_exception(traceback, args): #### Request Headers -
{headers}
""".format( +
{headers}
+ +--- + +#### Basic Information + +- **Site:** {site} +- **User:** {user} +- **Browser:** {user_agent.platform} {user_agent.browser} version: {user_agent.version} language: {user_agent.language} +- **Browser Languages**: `{accept_languages}`""".format( site=frappe.local.site, traceback=traceback, args="\n".join(pretty_args), @@ -268,14 +336,13 @@ def email_setup_wizard_exception(traceback, args): frappe.sendmail(recipients=frappe.local.conf.setup_wizard_exception_email, sender=frappe.session.user, - subject="Exception in Setup Wizard - {}".format(frappe.local.site), + subject="Setup failed: {}".format(frappe.local.site), message=message, delayed=False) def get_language_code(lang): return frappe.db.get_value('Language', {'language_name':lang}) - def enable_twofactor_all_roles(): all_role = frappe.get_doc('Role',{'role_name':'All'}) all_role.two_factor_auth = True diff --git a/frappe/public/css/page.css b/frappe/public/css/page.css index c31886e1cdd..9dd2c5b8663 100644 --- a/frappe/public/css/page.css +++ b/frappe/public/css/page.css @@ -279,6 +279,9 @@ select.input-sm { opacity: 1; cursor: pointer; } +.setup-wizard-slide .progress-bar { + background-color: #5e64ff; +} .page-card-container { padding: 70px; } @@ -312,49 +315,3 @@ select.input-sm { justify-content: center; align-items: center; } -@keyframes lds-rolling { - 0% { - -webkit-transform: translate(-50%, -50%) rotate(0deg); - transform: translate(-50%, -50%) rotate(0deg); - } - 100% { - -webkit-transform: translate(-50%, -50%) rotate(360deg); - transform: translate(-50%, -50%) rotate(360deg); - } -} -@-webkit-keyframes lds-rolling { - 0% { - -webkit-transform: translate(-50%, -50%) rotate(0deg); - transform: translate(-50%, -50%) rotate(0deg); - } - 100% { - -webkit-transform: translate(-50%, -50%) rotate(360deg); - transform: translate(-50%, -50%) rotate(360deg); - } -} -.lds-rolling { - -webkit-transform: translate(-100px, -100px) scale(1) translate(100px, 100px); - transform: translate(-100px, -100px) scale(1) translate(100px, 100px); -} -.lds-rolling div { - position: absolute; - width: 60px; - height: 60px; - border: 3px solid #d1d8dd; - border-top-color: transparent; - border-radius: 50%; - -webkit-animation: lds-rolling 1s linear infinite; - animation: lds-rolling 1s linear infinite; - top: 50px; - left: 50px; -} -.lds-rolling div:after { - position: absolute; - width: 60px; - height: 60px; - border: 3px solid #d1d8dd; - border-top-color: transparent; - border-radius: 50%; - -webkit-transform: rotate(90deg); - transform: rotate(90deg); -} diff --git a/frappe/public/less/page.less b/frappe/public/less/page.less index bd96d1e60e0..378f91e481d 100644 --- a/frappe/public/less/page.less +++ b/frappe/public/less/page.less @@ -335,6 +335,10 @@ select.input-sm { cursor: pointer; } } + + .progress-bar { + background-color: #5e64ff; + } } .page-card-container { @@ -376,50 +380,4 @@ select.input-sm { align-items: center; } -@keyframes lds-rolling { - 0% { - -webkit-transform: translate(-50%, -50%) rotate(0deg); - transform: translate(-50%, -50%) rotate(0deg); - } - 100% { - -webkit-transform: translate(-50%, -50%) rotate(360deg); - transform: translate(-50%, -50%) rotate(360deg); - } -} -@-webkit-keyframes lds-rolling { - 0% { - -webkit-transform: translate(-50%, -50%) rotate(0deg); - transform: translate(-50%, -50%) rotate(0deg); - } - 100% { - -webkit-transform: translate(-50%, -50%) rotate(360deg); - transform: translate(-50%, -50%) rotate(360deg); - } -} -.lds-rolling { - -webkit-transform: translate(-100px, -100px) scale(1) translate(100px, 100px); - transform: translate(-100px, -100px) scale(1) translate(100px, 100px); - div { - position: absolute; - width: 60px; - height: 60px; - border: 3px solid #d1d8dd; - border-top-color: transparent; - border-radius: 50%; - -webkit-animation: lds-rolling 1s linear infinite; - animation: lds-rolling 1s linear infinite; - top: 50px; - left: 50px; - &:after { - position: absolute; - width: 60px; - height: 60px; - border: 3px solid #d1d8dd; - border-top-color: transparent; - border-radius: 50%; - -webkit-transform: rotate(90deg); - transform: rotate(90deg); - } - } -}