Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[55178] Replace sidemenu of team planner and boards with Rails component #15895

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions app/components/open_project/common/submenu_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<% top_level_sidebar_menu_items.first.children.each do |menu_item| %>
<li class="op-sidemenu--item" data-filter--filter-list-target="searchItem">
<% selected = menu_item.selected ? 'selected' : '' %>
<a class="op-sidemenu--item-action <%= selected %>" href="<%= menu_item.href %>">
<a class="op-sidemenu--item-action <%= selected %>" href="<%= menu_item.href %>" data-test-selector="op-sidemenu--item-action">
<span class="op-sidemenu--item-title"><%= menu_item.title %></span>
</a>
</li>
Expand Down Expand Up @@ -60,7 +60,7 @@
<% menu_item.children.each do |child_item| %>
<li class="op-sidemenu--item" data-filter--filter-list-target="searchItem">
<% selected = child_item.selected ? 'selected' : '' %>
<a class="op-sidemenu--item-action <%= selected %>" href="<%= child_item.href %>">
<a class="op-sidemenu--item-action <%= selected %>" href="<%= child_item.href %>" data-test-selector="op-sidemenu--item-action">
<span class="op-sidemenu--item-title"><%= child_item.title %></span>
</a>
</li>
Expand All @@ -75,9 +75,14 @@
<%= render Primer::Beta::Button.new(scheme: :primary,
tag: :a,
href: @create_btn_options[:href],
test_selector: "#{@create_btn_options[:module_key]}--create-button",
classes: "op-sidebar--footer-action") do |button|
button.with_leading_visual_icon(icon: "plus")
@create_btn_options[:text]
if @create_btn_options[:btn_text].present?
@create_btn_options[:btn_text]
else
I18n.t("label_#{@create_btn_options[:module_key]}")
end
end %>
</div>
<% end %>
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/app/core/main-menu/submenu.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { TurboElement } from 'core-typings/turbo';
import { StateService } from '@uirouter/core';

@Injectable({ providedIn: 'root' })
export class SubmenuService {
constructor(protected $state:StateService) {}

reloadSubmenu():void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
const menuIdentifier:string|undefined = this.$state.current.data.sideMenuOptions?.sidemenuId;

if (menuIdentifier) {
const menu = (document.getElementById(menuIdentifier) as HTMLElement&TurboElement);
const currentSrc = menu.getAttribute('src');

if (currentSrc && menu) {
const frameUrl = new URL(currentSrc);

// Override the frame src to enforce a reload
menu.setAttribute('src', frameUrl.href);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
CustomDateActionAdminComponent,
customDateActionAdminSelector,
} from 'core-app/features/work-packages/components/wp-custom-actions/date-action/custom-date-action-admin.component';
import { BoardsMenuComponent, boardsMenuSelector } from 'core-app/features/boards/boards-sidebar/boards-menu.component';
import {
GlobalSearchWorkPackagesEntryComponent,
globalSearchWorkPackagesSelectorEntry,
Expand Down Expand Up @@ -143,10 +142,6 @@ import {
opInAppNotificationBellSelector,
} from 'core-app/features/in-app-notifications/bell/in-app-notification-bell.component';
import { IanMenuComponent, ianMenuSelector } from 'core-app/features/in-app-notifications/center/menu/menu.component';
import {
opTeamPlannerSidemenuSelector,
TeamPlannerSidemenuComponent,
} from 'core-app/features/team-planner/team-planner/sidemenu/team-planner-sidemenu.component';
import {
OpModalOverlayComponent,
opModalOverlaySelector,
Expand Down Expand Up @@ -187,7 +182,6 @@ export const globalDynamicComponents:OptionalBootstrapDefinition[] = [
{ selector: globalSearchWorkPackagesSelector, cls: GlobalSearchWorkPackagesComponent },
{ selector: homescreenNewFeaturesBlockSelector, cls: HomescreenNewFeaturesBlockComponent },
{ selector: customDateActionAdminSelector, cls: CustomDateActionAdminComponent },
{ selector: boardsMenuSelector, cls: BoardsMenuComponent },
{ selector: globalSearchWorkPackagesSelectorEntry, cls: GlobalSearchWorkPackagesEntryComponent },
{ selector: toastsContainerSelector, cls: ToastsContainerComponent },
{ selector: sidemenuSelector, cls: OpSidemenuComponent },
Expand All @@ -213,7 +207,6 @@ export const globalDynamicComponents:OptionalBootstrapDefinition[] = [
{ selector: headerProjectSelectSelector, cls: OpHeaderProjectSelectComponent },
{ selector: wpOverviewGraphSelector, cls: WorkPackageOverviewGraphComponent },
{ selector: opViewSelectSelector, cls: ViewSelectComponent },
{ selector: opTeamPlannerSidemenuSelector, cls: TeamPlannerSidemenuComponent },
{ selector: triggerActionsEntryComponentSelector, cls: TriggerActionsEntryComponent, embeddable: true },
{ selector: editableQueryPropsSelector, cls: EditableQueryPropsComponent },
{ selector: backupSelector, cls: BackupComponent },
Expand Down
6 changes: 1 addition & 5 deletions frontend/src/app/core/setup/globals/onboarding/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
export const onboardingTourStorageKey = 'openProject-onboardingTour';
export type OnboardingTourNames = 'prepareBacklogs'|'backlogs'|'taskboard'|'homescreen'|'workPackages'|'main';

export enum ProjectName {
demo = 'demo',
}
export type OnboardingTourNames = 'homescreen'|'workPackages'|'gantt'|'final'|'boards'|'teamPlanner';

function matchingFilter(list:NodeListOf<HTMLElement>, filterFunction:(match:HTMLElement) => boolean):HTMLElement|null {
for (let i = 0; i < list.length; i++) {
Expand Down
79 changes: 61 additions & 18 deletions frontend/src/app/core/setup/globals/onboarding/onboarding_tour.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import { wpOnboardingTourSteps } from 'core-app/core/setup/globals/onboarding/to
import {
OnboardingTourNames,
onboardingTourStorageKey,
ProjectName,
waitForElement,
} from 'core-app/core/setup/globals/onboarding/helpers';
import { boardTourSteps } from 'core-app/core/setup/globals/onboarding/tours/boards_tour';
import {
boardTourSteps,
navigateToBoardStep,
} from 'core-app/core/setup/globals/onboarding/tours/boards_tour';
import { menuTourSteps } from 'core-app/core/setup/globals/onboarding/tours/menu_tour';
import { homescreenOnboardingTourSteps } from 'core-app/core/setup/globals/onboarding/tours/homescreen_tour';
import { teamPlannerTourSteps } from 'core-app/core/setup/globals/onboarding/tours/team_planners_tour';
import {
navigateToTeamPlannerStep,
teamPlannerTourSteps,
} from 'core-app/core/setup/globals/onboarding/tours/team_planners_tour';
import { ganttOnboardingTourSteps } from 'core-app/core/setup/globals/onboarding/tours/gantt_tour';

require('core-vendor/enjoyhint');
Expand Down Expand Up @@ -36,9 +41,11 @@ export type OnboardingStep = {
};

function initializeTour(storageValue:string) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
window.onboardingTourInstance = new window.EnjoyHint({
onStart() {
jQuery('#content-wrapper, #menu-sidebar').addClass('-hidden-overflow');
sessionStorage.setItem(onboardingTourStorageKey, storageValue);
},
onEnd() {
sessionStorage.setItem(onboardingTourStorageKey, storageValue);
Expand All @@ -56,8 +63,8 @@ function startTour(steps:OnboardingStep[]) {
window.onboardingTourInstance.run();
}

function moduleVisible(name:string):boolean {
return document.getElementsByClassName(`${name}-menu-item`).length > 0;
export function moduleVisible(name:string):boolean {
return document.querySelector(`#menu-sidebar .${name}-menu-item`) !== null;
}

function workPackageTour() {
Expand All @@ -68,39 +75,69 @@ function workPackageTour() {
startTour(steps);
});
}
function mainTour(project:ProjectName = ProjectName.demo) {
initializeTour('mainTourFinished');

function ganttTour() {
initializeTour('ganttTourFinished');

const boardsDemoDataAvailable = jQuery('meta[name=boards_demo_data_available]').attr('content') === 'true';
const teamPlannerDemoDataAvailable = jQuery('meta[name=demo_view_of_type_team_planner_seeded]').attr('content') === 'true';
const eeTokenAvailable = !jQuery('body').hasClass('ee-banners-visible');

waitForElement('.work-package--results-tbody', '#content', () => {
let steps:OnboardingStep[] = ganttOnboardingTourSteps();

// Check for EE edition
if (eeTokenAvailable) {
// ... and available seed data of boards.
// Then add boards to the tour, otherwise skip it.
if (boardsDemoDataAvailable && moduleVisible('boards')) {
steps = steps.concat(boardTourSteps('enterprise', project));
}

// ... same for team planners
if (teamPlannerDemoDataAvailable && moduleVisible('team-planner-view')) {
steps = steps.concat(teamPlannerTourSteps());
steps = steps.concat(navigateToBoardStep('enterprise'));
} else if (teamPlannerDemoDataAvailable && moduleVisible('team-planner-view')) {
steps = steps.concat(navigateToTeamPlannerStep());
} else {
steps = steps.concat(menuTourSteps());
}
} else if (boardsDemoDataAvailable && moduleVisible('boards')) {
steps = steps.concat(boardTourSteps('basic', project));
steps = steps.concat(navigateToBoardStep('basic'));
} else {
steps = steps.concat(menuTourSteps());
}

startTour(steps);
});
}

function boardTour() {
initializeTour('boardsTourFinished');

const teamPlannerDemoDataAvailable = jQuery('meta[name=demo_view_of_type_team_planner_seeded]').attr('content') === 'true';
const eeTokenAvailable = !jQuery('body').hasClass('ee-banners-visible');

waitForElement('wp-single-card', '#content', () => {
let steps:OnboardingStep[] = eeTokenAvailable ? boardTourSteps('enterprise') : boardTourSteps('basic');

// Available seed data of team planner.
// Then add Team planner to the tour, otherwise skip it.
if (teamPlannerDemoDataAvailable && moduleVisible('team-planner-view')) {
steps = steps.concat(navigateToTeamPlannerStep());
} else {
steps = steps.concat(menuTourSteps());
}

startTour(steps);
});
}

function teamPlannerTour() {
initializeTour('teamPlannerTourFinished');
waitForElement('full-calendar', '#content', () => {
let steps:OnboardingStep[] = teamPlannerTourSteps();
steps = steps.concat(menuTourSteps());

startTour(steps);
});
}

export function start(name:OnboardingTourNames, project?:ProjectName):void {
export function start(name:OnboardingTourNames):void {
switch (name) {
case 'homescreen':
initializeTour('startProjectTour');
Expand All @@ -109,8 +146,14 @@ export function start(name:OnboardingTourNames, project?:ProjectName):void {
case 'workPackages':
workPackageTour();
break;
case 'main':
mainTour(project);
case 'gantt':
ganttTour();
break;
case 'boards':
boardTour();
break;
case 'teamPlanner':
teamPlannerTour();
break;
default:
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
import {
OnboardingTourNames,
onboardingTourStorageKey,
ProjectName,
waitForElement,
} from 'core-app/core/setup/globals/onboarding/helpers';
import { debugLog } from 'core-app/shared/helpers/debug_output';

async function triggerTour(name:OnboardingTourNames, project?:ProjectName):Promise<void> {
async function triggerTour(name:OnboardingTourNames):Promise<void> {
debugLog(`Loading and triggering onboarding tour ${name}`);
await import(/* webpackChunkName: "onboarding-tour" */ './onboarding_tour').then((tour) => {
tour.start(name, project);
tour.start(name);
});
}

Expand Down Expand Up @@ -67,12 +66,40 @@ export function detectOnboardingTour():void {

// ------------------------------- Tutorial WP page -------------------------------
if (url.searchParams.get('start_onboarding_tour')) {
void triggerTour('workPackages', ProjectName.demo);
void triggerTour('workPackages');
}

// ------------------------------- Tutorial Main part (starting from the Gantt module) -------------------------------
// ------------------------------- Tutorial Gantt module -------------------------------
if (currentTourPart === 'wpTourFinished') {
void triggerTour('main', ProjectName.demo);
void triggerTour('gantt');
return;
}

// ------------------------------- Tutorial Boards module -------------------------------
if (currentTourPart === 'ganttTourFinished') {
if (url.pathname.includes('boards')) {
void triggerTour('boards');
return;
}
if (url.pathname.includes('team_planner')) {
void triggerTour('teamPlanner');
return;
}
void triggerTour('final');
}

// ------------------------------- Tutorial TeamPlanner module -------------------------------
if (currentTourPart === 'boardsTourFinished') {
if (url.pathname.includes('team_planner')) {
void triggerTour('teamPlanner');
return;
}
void triggerTour('final');
}

// ------------------------------- Fina tutorial -------------------------------
if (currentTourPart === 'teamPlannerTourFinished') {
void triggerTour('final');
}
}
}
48 changes: 25 additions & 23 deletions frontend/src/app/core/setup/globals/onboarding/tours/boards_tour.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,12 @@
import {
ProjectName,
waitForElement,
} from 'core-app/core/setup/globals/onboarding/helpers';
import { OnboardingStep } from 'core-app/core/setup/globals/onboarding/onboarding_tour';

export function boardTourSteps(edition:'basic'|'enterprise', project:ProjectName):OnboardingStep[] {
let boardName:string;
if (edition === 'basic') {
boardName = project === ProjectName.demo ? 'Basic board' : 'Task board';
} else {
boardName = 'Kanban';
}

export function boardTourSteps(edition:'basic'|'enterprise'):OnboardingStep[] {
const listExplanation = edition === 'basic' ? 'basic' : 'kanban';

return [
{
'next #boards-wrapper>.boards-menu-item': I18n.t('js.onboarding.steps.boards.overview'),
showSkip: false,
nextButton: { text: I18n.t('js.onboarding.buttons.next') },
onNext() {
jQuery('#boards-wrapper>.boards-menu-item ~ .toggler')[0].click();
waitForElement(
'.op-sidemenu--item-action',
'#main-menu',
(match) => match.click(),
(match) => !!match.textContent?.includes(boardName),
);
},
},
{
'next [data-tour-selector="op-board-list"]': I18n.t(`js.onboarding.steps.boards.lists_${listExplanation}`),
showSkip: false,
Expand Down Expand Up @@ -57,3 +35,27 @@ export function boardTourSteps(edition:'basic'|'enterprise', project:ProjectName
},
];
}

export function navigateToBoardStep(edition:'basic'|'enterprise'):OnboardingStep {
let boardName:string;
if (edition === 'basic') {
boardName = 'Basic board';
} else {
boardName = 'Kanban';
}

return {
'next #boards-wrapper>.boards-menu-item': I18n.t('js.onboarding.steps.boards.overview'),
showSkip: false,
nextButton: { text: I18n.t('js.onboarding.buttons.next') },
onNext() {
jQuery('#boards-wrapper>.boards-menu-item ~ .toggler')[0].click();
waitForElement(
'.op-sidemenu--item-action',
'#main-menu',
(match) => match.click(),
(match) => !!match.textContent?.includes(boardName),
);
},
};
}
Loading
Loading