fix: cy test case failing

This commit is contained in:
Rohit Waghchaure 2026-02-23 12:52:47 +05:30
parent c859375d2c
commit 97e4e46ec4
9 changed files with 195 additions and 100 deletions

View file

@ -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),

View file

@ -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",

View file

@ -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

View file

@ -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";

View file

@ -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>

View file

@ -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();
});
}

View file

@ -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>

View file

@ -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;
}
`;

View file

@ -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 {