fix: cy test case failing
This commit is contained in:
parent
c859375d2c
commit
97e4e46ec4
9 changed files with 195 additions and 100 deletions
|
|
@ -51,9 +51,13 @@ class Workspace:
|
|||
|
||||
self.allowed_pages = get_allowed_pages(cache=True)
|
||||
self.allowed_reports = get_allowed_reports(cache=True)
|
||||
self.onboarding_list = self.get_onboarding_list()
|
||||
|
||||
if not minimal:
|
||||
if self.doc.content:
|
||||
self.onboarding_list = [
|
||||
x["data"]["onboarding_name"] for x in loads(self.doc.content) if x["type"] == "onboarding"
|
||||
]
|
||||
|
||||
self.table_counts = get_table_with_counts()
|
||||
self.restricted_doctypes = (
|
||||
frappe.cache.get_value("domain_restricted_doctypes") or build_domain_restricted_doctype_cache()
|
||||
|
|
@ -62,14 +66,6 @@ class Workspace:
|
|||
frappe.cache.get_value("domain_restricted_pages") or build_domain_restricted_page_cache()
|
||||
)
|
||||
|
||||
def get_onboarding_list(self):
|
||||
return frappe.get_all(
|
||||
"Module Onboarding",
|
||||
filters={"is_complete": 0, "module": self.page_name},
|
||||
pluck="name",
|
||||
order_by="creation",
|
||||
)
|
||||
|
||||
def is_permitted(self):
|
||||
"""Return true if `Has Role` is not set or the user is allowed."""
|
||||
from frappe.utils import has_common
|
||||
|
|
@ -160,6 +156,7 @@ class Workspace:
|
|||
self.cards = {"items": self.get_links()}
|
||||
self.charts = {"items": self.get_charts()}
|
||||
self.shortcuts = {"items": self.get_shortcuts()}
|
||||
self.onboardings = {"items": []}
|
||||
self.quick_lists = {"items": self.get_quick_lists()}
|
||||
self.number_cards = {"items": self.get_number_cards()}
|
||||
self.custom_blocks = {"items": self.get_custom_blocks()}
|
||||
|
|
@ -370,6 +367,7 @@ def get_desktop_page(page: str):
|
|||
"charts": workspace.charts,
|
||||
"shortcuts": workspace.shortcuts,
|
||||
"cards": workspace.cards,
|
||||
"onboardings": workspace.onboardings,
|
||||
"quick_lists": workspace.quick_lists,
|
||||
"number_cards": workspace.number_cards,
|
||||
"custom_blocks": workspace.custom_blocks,
|
||||
|
|
@ -650,7 +648,7 @@ def prepare_widget(config, doctype, parentfield):
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_onboarding_step(name: str, field: str, value: any):
|
||||
def update_onboarding_step(name: str | int, field: str, value: int | str):
|
||||
"""Update status of onboaridng step
|
||||
|
||||
Args:
|
||||
|
|
@ -687,6 +685,12 @@ def get_onboarding_data(module: str):
|
|||
if onboarding_doc.is_complete:
|
||||
return []
|
||||
|
||||
# Check if user is allowed
|
||||
allowed_roles = set(onboarding_doc.get_allowed_roles())
|
||||
user_roles = set(frappe.get_roles())
|
||||
if not allowed_roles & user_roles:
|
||||
return None
|
||||
|
||||
item = {
|
||||
"label": _(module),
|
||||
"title": _(onboarding_doc.title),
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@
|
|||
"validate_action",
|
||||
"field",
|
||||
"value_to_validate",
|
||||
"video_url"
|
||||
"video_url",
|
||||
"section_break_ajog",
|
||||
"route_options"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -61,7 +63,7 @@
|
|||
"fieldname": "action",
|
||||
"fieldtype": "Select",
|
||||
"label": "Action",
|
||||
"options": "Create Entry\nUpdate Settings\nShow Form Tour\nView Report\nGo to Page\nWatch Video",
|
||||
"options": "Create Entry\nUpdate Settings\nShow Form Tour\nView Report\nGo to Page\nView Docs",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
|
|
@ -145,7 +147,7 @@
|
|||
"label": "Is Single"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.action == \"Go to Page\"",
|
||||
"depends_on": "eval:doc.action == \"Go to Page\" || doc.action === \"View Docs\"",
|
||||
"description": "Example: #Tree/Account",
|
||||
"fieldname": "path",
|
||||
"fieldtype": "Data",
|
||||
|
|
@ -214,10 +216,19 @@
|
|||
"fieldtype": "Link",
|
||||
"label": "Form Tour",
|
||||
"options": "Form Tour"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ajog",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "route_options",
|
||||
"fieldtype": "Code",
|
||||
"label": "Route Options"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2026-02-21 08:37:30.532549",
|
||||
"modified": "2026-02-23 21:03:51.131292",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Onboarding Step",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class OnboardingStep(Document):
|
|||
from frappe.types import DF
|
||||
|
||||
action: DF.Literal[
|
||||
"Create Entry", "Update Settings", "Show Form Tour", "View Report", "Go to Page", "Watch Video"
|
||||
"Create Entry", "Update Settings", "Show Form Tour", "View Report", "Go to Page", "View Docs"
|
||||
]
|
||||
action_label: DF.Data | None
|
||||
callback_message: DF.SmallText | None
|
||||
|
|
@ -36,6 +36,7 @@ class OnboardingStep(Document):
|
|||
report_description: DF.Data | None
|
||||
report_reference_doctype: DF.Data | None
|
||||
report_type: DF.Data | None
|
||||
route_options: DF.Code | None
|
||||
show_form_tour: DF.Check
|
||||
show_full_form: DF.Check
|
||||
title: DF.Data
|
||||
|
|
|
|||
|
|
@ -109,8 +109,8 @@ import "./frappe/utils/dashboard_utils.js";
|
|||
import "./frappe/ui/chart.js";
|
||||
import "./frappe/ui/datatable.js";
|
||||
import "./frappe/ui/driver.js";
|
||||
import "./frappe/ui/user_onboarding/user_onboarding.bundle.js";
|
||||
import "./frappe/scanner";
|
||||
|
||||
import "./frappe/ui/address_autocomplete/autocomplete_dialog.js";
|
||||
import "./frappe/ui/desktop_icon.html";
|
||||
import "./frappe/ui/user_onboarding/user_onboarding.bundle.js";
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@
|
|||
</div>
|
||||
<p>
|
||||
<a class="onboarding-sidebar">
|
||||
{%= frappe.utils.icon("getting-started" , "sm", "", "", "text-ink-gray-7 current-color", true)%}
|
||||
<span> {%= __("Continue Onboarding") %} </span>
|
||||
{%= frappe.utils.icon("user-check" , "sm", "", "", "text-ink-gray-7 current-color", true)%}
|
||||
<span> {%= __("Getting started") %} </span>
|
||||
</a>
|
||||
</p>
|
||||
<a class="collapse-sidebar-link">
|
||||
|
|
@ -90,6 +90,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="overlay" style="z-index: 1021;"></div>
|
||||
<div class="user_onboarding"></div>
|
||||
<div class="user-onboarding"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -78,26 +78,49 @@ frappe.ui.Sidebar = class Sidebar {
|
|||
}
|
||||
}
|
||||
|
||||
setup_onboarding() {
|
||||
let me = this;
|
||||
this.$onboarding = this.wrapper.find(".user_onboarding");
|
||||
remove_onboarding_wrapper() {
|
||||
this.$onboarding.empty();
|
||||
this.wrapper.find(".onboarding-sidebar").removeClass("hidden");
|
||||
}
|
||||
|
||||
setup_onboarding() {
|
||||
let me = this;
|
||||
this.$onboarding = this.wrapper.find(".user-onboarding");
|
||||
|
||||
if (!this.sidebar_data || !this.sidebar_data.module_onboarding) {
|
||||
this.remove_onboarding_wrapper();
|
||||
return;
|
||||
}
|
||||
|
||||
let module_name = this.sidebar_data.module_onboarding;
|
||||
|
||||
if (this?.onboarding_widget[module_name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.remove_onboarding_wrapper();
|
||||
if (module_name) {
|
||||
if (
|
||||
this?.onboarding_widget[module_name] &&
|
||||
this.onboarding_widget[module_name].hide_panel
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.sidebar_data && this.sidebar_data.module_onboarding) {
|
||||
return frappe
|
||||
.call({
|
||||
method: "frappe.desk.desktop.get_onboarding_data",
|
||||
args: {
|
||||
// send sorted min requirements to increase chance of cache hit
|
||||
module: this.sidebar_data.module_onboarding,
|
||||
module: module_name,
|
||||
},
|
||||
type: "GET",
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.message?.length > 0) {
|
||||
let onboarding_data = data.message[0];
|
||||
me.onboarding_widget = new frappe.ui.UserOnboarding({
|
||||
me.onboarding_widget = {};
|
||||
me.onboarding_widget[module_name] = new frappe.ui.UserOnboarding({
|
||||
title: onboarding_data.title,
|
||||
steps: onboarding_data.items,
|
||||
wrapper: me.$onboarding,
|
||||
|
|
@ -133,6 +156,10 @@ frappe.ui.Sidebar = class Sidebar {
|
|||
this.workspace_sidebar_items = updated_items;
|
||||
}
|
||||
setup(workspace_title) {
|
||||
if (!this.onboarding_widget) {
|
||||
this.onboarding_widget = {};
|
||||
}
|
||||
|
||||
$(document).trigger("sidebar_setup", { sidebar: this });
|
||||
this.sidebar_title = workspace_title;
|
||||
this.check_for_private_workspace(workspace_title);
|
||||
|
|
@ -146,6 +173,10 @@ frappe.ui.Sidebar = class Sidebar {
|
|||
this.setup_onboarding();
|
||||
|
||||
this.wrapper.find(".onboarding-sidebar").click(() => {
|
||||
if (this.sidebar_data?.module_onboarding) {
|
||||
delete this.onboarding_widget[this.sidebar_data.module_onboarding];
|
||||
}
|
||||
|
||||
this.setup_onboarding();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ const visible = computed({
|
|||
set: (val) => emit("update:modelValue", val),
|
||||
});
|
||||
|
||||
let skippAll = false;
|
||||
|
||||
const completedCount = computed(
|
||||
() => props.steps.filter((step) => step.is_complete || step.is_skipped).length
|
||||
);
|
||||
|
|
@ -68,10 +70,27 @@ function skipAll(skips) {
|
|||
markSkip(step);
|
||||
}
|
||||
});
|
||||
|
||||
skippAll = true;
|
||||
}
|
||||
|
||||
function resetAll(skips) {
|
||||
skips.forEach((step) => {
|
||||
if (!step.is_complete && step.is_skipped) {
|
||||
markReset(step);
|
||||
}
|
||||
});
|
||||
|
||||
skippAll = false;
|
||||
}
|
||||
|
||||
function handleAction(step) {
|
||||
if (step.is_complete) return;
|
||||
if (step.is_skipped) return;
|
||||
|
||||
if (step.route_options && typeof step.route_options === "string") {
|
||||
frappe.route_options = JSON.parse(step.route_options);
|
||||
}
|
||||
|
||||
const actions = {
|
||||
"Create Entry": createEntry,
|
||||
|
|
@ -79,6 +98,7 @@ function handleAction(step) {
|
|||
"Update Settings": updateSettings,
|
||||
"View Report": openReport,
|
||||
"Go to Page": goToPage,
|
||||
"View Docs": viewDocs,
|
||||
};
|
||||
|
||||
if (step.action && actions[step.action]) {
|
||||
|
|
@ -88,13 +108,22 @@ function handleAction(step) {
|
|||
}
|
||||
}
|
||||
|
||||
function viewDocs(step) {
|
||||
window.open(step.path, "_blank");
|
||||
markComplete(step);
|
||||
}
|
||||
|
||||
function goToPage(step) {
|
||||
toggleCollapse();
|
||||
|
||||
frappe.set_route(step.path).then(() => {
|
||||
markComplete(step);
|
||||
});
|
||||
}
|
||||
|
||||
function openReport(step) {
|
||||
toggleCollapse();
|
||||
|
||||
const route = frappe.utils.generate_route({
|
||||
name: step.reference_report,
|
||||
type: "report",
|
||||
|
|
@ -162,7 +191,6 @@ async function createEntry(step) {
|
|||
};
|
||||
|
||||
frappe.route_hooks.after_save = callback;
|
||||
|
||||
if (step.show_full_form) {
|
||||
frappe.set_route("Form", step.reference_document, "new");
|
||||
} else {
|
||||
|
|
@ -204,31 +232,42 @@ function markReset(step) {
|
|||
<template>
|
||||
<div v-if="visible" class="onb-panel">
|
||||
<!-- Header -->
|
||||
<div class="header onb-header-main">
|
||||
<div class="onb-header-left">
|
||||
<div class="onb-header-logo" v-html="headerIcon"></div>
|
||||
<h4 class="onb-header-title">{{ title }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="header onb-header-main">
|
||||
<div class="text-base font-medium">Getting started</div>
|
||||
<div class="onb-header-actions">
|
||||
<button @click="toggleCollapse" v-html="minimizeIcon"></button>
|
||||
<button @click="close" v-html="closeIcon"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div v-if="!collapsed" class="body">
|
||||
<div class="intro">
|
||||
<p>{{ completedCount }}/{{ steps.length }} steps completed</p>
|
||||
<div class="onb-title">
|
||||
<div class="onb-title-icon" v-html="headerIcon"></div>
|
||||
|
||||
<div class="text-base font-medium">{{ title }}</div>
|
||||
|
||||
<div class="onb-title-steps">
|
||||
{{ completedCount }}/{{ steps.length }} steps completed
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="onb-progress">
|
||||
<div class="onboarding-progress-bar" :style="{ width: progress + '%' }"></div>
|
||||
</div>
|
||||
<div class="onb-progress-row">
|
||||
<div v-if="progress !== 100">
|
||||
<div class="onb-progress-badge">{{ progress }}% {{ __("completed") }}</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="onb-progress-badge-complete">
|
||||
{{ progress }}% {{ __("completed") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="onb-progress-label">
|
||||
{{ progress }}% completed
|
||||
<span class="onb-skip" @click="skipAll(steps)">Skip all</span>
|
||||
<div v-if="skippAll">
|
||||
<span class="onb-skip" @click="resetAll(steps)"> {{ __("Reset all") }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span class="onb-skip" @click="skipAll(steps)">Skip all</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Steps -->
|
||||
|
|
@ -248,7 +287,7 @@ function markReset(step) {
|
|||
: 'text-ink-gray-8 onb-select-cursor'
|
||||
"
|
||||
>
|
||||
<div class="onb-step-left">
|
||||
<div class="onb-step-left" @click="handleAction(step)">
|
||||
<div class="onb-step-icon" v-if="step.is_complete">
|
||||
<div v-html="completeChecklistIcon"></div>
|
||||
</div>
|
||||
|
|
@ -257,7 +296,7 @@ function markReset(step) {
|
|||
</div>
|
||||
|
||||
<div v-if="!step.is_skipped">
|
||||
<span class="text-base onb-step-text" @click="handleAction(step)">
|
||||
<span class="text-base onb-step-text">
|
||||
{{ step.action_label }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ class UserOnboarding {
|
|||
this.$wrapper = $(wrapper);
|
||||
this.header_icon = header_icon;
|
||||
this.init();
|
||||
this.hide_panel = false;
|
||||
}
|
||||
|
||||
init() {
|
||||
|
|
@ -16,6 +17,7 @@ class UserOnboarding {
|
|||
let title = this.title || __("Welcome to Frappe!");
|
||||
let onboarding_checklist = this.steps || [];
|
||||
let header_icon = this.header_icon;
|
||||
let me = this;
|
||||
|
||||
const app = createApp({
|
||||
components: { OnboardingPanel },
|
||||
|
|
@ -41,7 +43,10 @@ class UserOnboarding {
|
|||
"",
|
||||
"var(--green)"
|
||||
),
|
||||
"onUpdate:modelValue": (v) => (showPanel.value = v),
|
||||
"onUpdate:modelValue": (v) => {
|
||||
showPanel.value = v;
|
||||
me.hide_panel = !v;
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -80,23 +85,6 @@ function addStyles() {
|
|||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.onb-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.onb-header-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.onb-header-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.onb-header-actions button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
|
|
@ -108,21 +96,26 @@ function addStyles() {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1; /* takes remaining space */
|
||||
min-width: 0; /* allows truncation */
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.onb-step-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.onb-step-icon {
|
||||
margin-bottom: 2px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: 14px;
|
||||
line-spacing: 1.15;
|
||||
letter-spacing: 0.02em;
|
||||
color: #050505;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.onb-step-text {
|
||||
white-space: nowrap;
|
||||
margin-top: 2px;
|
||||
|
|
@ -130,22 +123,6 @@ function addStyles() {
|
|||
font-size: 14px;
|
||||
}
|
||||
|
||||
.onb-progress {
|
||||
height: 6px;
|
||||
background: #eee;
|
||||
border-radius: 4px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.onb-progress-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.onb-skip {
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
|
|
@ -156,12 +133,6 @@ function addStyles() {
|
|||
color: #111827;
|
||||
}
|
||||
|
||||
.onboarding-progress-bar {
|
||||
height: 100%;
|
||||
background: #ffcd78;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.onb-steps {
|
||||
margin-top: 16px;
|
||||
padding: 0;
|
||||
|
|
@ -196,20 +167,53 @@ function addStyles() {
|
|||
visibility: visible;
|
||||
}
|
||||
|
||||
.onb-header-logo {
|
||||
.onb-title {
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.onb-title-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.onb-title-steps {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.onb-progress-badge {
|
||||
background: #FDFAED;
|
||||
color: #DB7706;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.onb-progress-badge-complete {
|
||||
background: #E4FAEB;
|
||||
color: #278F5E;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.onb-progress-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 14px 0 8px;
|
||||
}
|
||||
|
||||
.onb-header-logo img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.onb-header-logo h4 {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
.onb-progress-text {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,9 +58,14 @@ div#driver-popover-item {
|
|||
}
|
||||
}
|
||||
|
||||
#driver-page-overlay {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
#driver-highlighted-element-stage {
|
||||
background-color: var(--bg-color) !important;
|
||||
border-radius: var(--border-radius) !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
input.driver-highlighted-element {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue