From 0c671db829ed785d3da4ca90051b20e06eb82cbc Mon Sep 17 00:00:00 2001 From: Adilet Soronov <74559101+adiletelf@users.noreply.github.com> Date: Wed, 22 May 2024 17:43:08 +0600 Subject: [PATCH] Add toggle 'Treat as end of fiscal year' --- CHANGELOG.md | 3 +++ capabilities.json | 5 ++++ package-lock.json | 4 +-- package.json | 2 +- pbiviz.json | 4 +-- src/calendars/calendar.ts | 36 ++++++++++++++++++++----- src/calendars/calendarFactory.ts | 9 ++++--- src/calendars/calendarISO8061.ts | 6 ++--- src/timeLine.ts | 9 ++++--- src/timeLineSettingsModel.ts | 9 ++++++- stringResources/en-US/resources.resjson | 1 + test/visual.test.ts | 28 ++++++++++--------- 12 files changed, 81 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d16aecb..2baca4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.5.5 +* Add toggle 'Treat as end of fiscal year' + ## 2.5.4 * Disable "Calendar" and "First day of week" settings when WeekStandard is set to IS0 860 * Fix uninitialized start date when date from filters is less than date from DataView diff --git a/capabilities.json b/capabilities.json index 4e4fd97..280e2f2 100644 --- a/capabilities.json +++ b/capabilities.json @@ -99,6 +99,11 @@ }, "calendar": { "properties": { + "treatAsEndOfFiscalYear": { + "type": { + "bool": true + } + }, "month": { "type": { "enumeration": [ diff --git a/package-lock.json b/package-lock.json index c5421b3..d48781b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "powerbi-visuals-timeline", - "version": "2.5.4.0", + "version": "2.5.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "powerbi-visuals-timeline", - "version": "2.5.4.0", + "version": "2.5.5.0", "license": "MIT", "dependencies": { "@typescript-eslint/eslint-plugin": "^7.7.1", diff --git a/package.json b/package.json index 2bc2c8c..bcefd5e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "powerbi-visuals-timeline", - "version": "2.5.4.0", + "version": "2.5.5.0", "description": "Timeline slicer is a graphical date range selector used as a filtering component in the report canvas", "repository": { "type": "git", diff --git a/pbiviz.json b/pbiviz.json index e0515cf..481635b 100644 --- a/pbiviz.json +++ b/pbiviz.json @@ -1,10 +1,10 @@ { "visual": { "name": "Timeline", - "displayName": "Timeline 2.5.4.0", + "displayName": "Timeline 2.5.5.0", "guid": "Timeline1447991079100", "visualClassName": "Timeline", - "version": "2.5.4.0", + "version": "2.5.5.0", "description": "Timeline slicer is a graphical date range selector used as a filtering component in the report canvas", "supportUrl": "https://community.powerbi.com", "gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-timeline" diff --git a/src/calendars/calendar.ts b/src/calendars/calendar.ts index 86178c4..9d07213 100644 --- a/src/calendars/calendar.ts +++ b/src/calendars/calendar.ts @@ -47,8 +47,13 @@ export interface WeekdayFormat { day: number; } +export interface CalendarFormattingSettings { + treatAsEndOfFiscalYear: boolean; +} + export class Calendar { private static QuarterFirstMonths: number[] = [0, 3, 6, 9]; + private settings: CalendarFormattingSettings; protected firstDayOfWeek: number; protected firstMonthOfYear: number; @@ -60,7 +65,8 @@ export class Calendar { protected EmptyYearOffset: number = 0; protected YearOffset: number = 1; - constructor(calendarFormat: CalendarFormat, weekDaySettings: WeekdayFormat) { + constructor(calendarFormat: CalendarFormat, weekDaySettings: WeekdayFormat, settings: CalendarFormattingSettings) { + this.settings = settings; this.isDaySelection = weekDaySettings.daySelection; this.firstDayOfWeek = weekDaySettings.day; this.firstMonthOfYear = calendarFormat.month; @@ -78,7 +84,11 @@ export class Calendar { const firstMonthOfYear = this.getFirstMonthOfYear(); const firstDayOfYear = this.getFirstDayOfYear(); - return ((firstMonthOfYear === 0 && firstDayOfYear === 1) ? 0 : 1); + if (firstMonthOfYear === 0 && firstDayOfYear === 1) { + return 0; + } + + return this.settings.treatAsEndOfFiscalYear ? 1 : -1; } public determineYear(date: Date): number { @@ -91,9 +101,18 @@ export class Calendar { firstDayOfYear, ); - return date.getFullYear() + this.getFiscalYearAdjustment() - ((firstDate <= date) - ? this.EmptyYearOffset - : this.YearOffset); + let adjustment: number = this.getFiscalYearAdjustment(); + + if (adjustment === 0) { + return date.getFullYear(); + } + else if (this.settings.treatAsEndOfFiscalYear) { + adjustment -= ((firstDate <= date) ? this.EmptyYearOffset : this.YearOffset); + } else { + adjustment += ((firstDate > date) ? this.EmptyYearOffset : this.YearOffset); + } + + return date.getFullYear() + adjustment; } public determineWeek(date: Date): number[] { @@ -101,7 +120,12 @@ export class Calendar { // It's Ok until this year is used to calculate date of first week. // So, here is some adjustment was applied. const year: number = this.determineYear(date); - const fiscalYearAdjustment = this.getFiscalYearAdjustment(); + let fiscalYearAdjustment = this.getFiscalYearAdjustment(); + + // fiscal year starts with W1 (week 1), meaning previous week should be W52-53 + if (fiscalYearAdjustment === -1) { + fiscalYearAdjustment = 0; + } const dateOfFirstWeek: Date = this.getDateOfFirstWeek(year - fiscalYearAdjustment); const dateOfFirstFullWeek: Date = this.getDateOfFirstFullWeek(year - fiscalYearAdjustment); diff --git a/src/calendars/calendarFactory.ts b/src/calendars/calendarFactory.ts index 608afd8..e9d9095 100644 --- a/src/calendars/calendarFactory.ts +++ b/src/calendars/calendarFactory.ts @@ -1,4 +1,4 @@ -import {Calendar, CalendarFormat, WeekdayFormat} from "./calendar"; +import {Calendar, CalendarFormat, CalendarFormattingSettings, WeekdayFormat} from "./calendar"; import { WeekStandard } from "./weekStandard"; import { CalendarISO8061 } from "./calendarISO8061"; @@ -6,16 +6,17 @@ export class CalendarFactory { public create( weekStandard: WeekStandard, calendarSettings: CalendarFormat, - weekDaySettings: WeekdayFormat) : Calendar { + weekDaySettings: WeekdayFormat, + settings: CalendarFormattingSettings) : Calendar { let calendar: Calendar; switch (weekStandard) { case WeekStandard.ISO8061: - calendar = new CalendarISO8061(); + calendar = new CalendarISO8061(settings); break; default: - calendar = new Calendar(calendarSettings, weekDaySettings) + calendar = new Calendar(calendarSettings, weekDaySettings, settings) } return calendar; diff --git a/src/calendars/calendarISO8061.ts b/src/calendars/calendarISO8061.ts index 6baff72..ec8b218 100644 --- a/src/calendars/calendarISO8061.ts +++ b/src/calendars/calendarISO8061.ts @@ -1,11 +1,11 @@ -import {Calendar, CalendarFormat, WeekdayFormat} from "./calendar"; +import {Calendar, CalendarFormat, CalendarFormattingSettings, WeekdayFormat} from "./calendar"; import { WeekStandard } from "./weekStandard"; import { Utils } from "../utils"; import {CalendarSettingsCard} from "../timeLineSettingsModel"; export class CalendarISO8061 extends Calendar { - constructor() { + constructor(settings: CalendarFormattingSettings) { const isoCalendarSettings: CalendarFormat = { month: CalendarSettingsCard.DefaultMonth, day: CalendarSettingsCard.DefaultDay, @@ -16,7 +16,7 @@ export class CalendarISO8061 extends Calendar { day: 1, }; - super(isoCalendarSettings, isoWeekDaySettings); + super(isoCalendarSettings, isoWeekDaySettings, settings); //this.firstDayOfYear = calendarFormat.day; } diff --git a/src/timeLine.ts b/src/timeLine.ts index 1fbd3f1..b764b78 100644 --- a/src/timeLine.ts +++ b/src/timeLine.ts @@ -66,7 +66,7 @@ import {ITimelineDatePeriod, ITimelineDatePeriodBase,} from "./datePeriod/datePe import {DatePeriodBase} from "./datePeriod/datePeriodBase"; -import {Calendar, CalendarFormat, WeekdayFormat} from "./calendars/calendar"; +import {Calendar, CalendarFormat, CalendarFormattingSettings, WeekdayFormat} from "./calendars/calendar"; import {Utils} from "./utils"; import {WeekStandard} from "./calendars/weekStandard"; import {CalendarFactory} from "./calendars/calendarFactory"; @@ -150,7 +150,9 @@ export class Timeline implements powerbiVisualsApi.extensibility.visual.IVisual } if (!initialized || isCalendarChanged) { - calendar = new CalendarFactory().create(weekStandard, calendarFormat, weekDayFormat); + const calendarFormattingSettings: CalendarFormattingSettings = { treatAsEndOfFiscalYear: this.visualSettings.calendar.treatAsEndOfFiscalYear.value }; + + calendar = new CalendarFactory().create(weekStandard, calendarFormat, weekDayFormat, calendarFormattingSettings); const granularity: GranularityType = this.visualSettings.granularity.granularity.value ? this.visualSettings.granularity.granularity.value.value : GranularityType.month; @@ -1234,7 +1236,8 @@ export class Timeline implements powerbiVisualsApi.extensibility.visual.IVisual ) { const { weekStandard, calendarFormat, weekDayFormat } = Timeline.computeCalendarFormat(timelineSettings); - const calendar: Calendar = this.calendarFactory.create(weekStandard, calendarFormat, weekDayFormat); + const calendarFormattingSettings: CalendarFormattingSettings = { treatAsEndOfFiscalYear: timelineSettings.calendar.treatAsEndOfFiscalYear.value }; + const calendar: Calendar = this.calendarFactory.create(weekStandard, calendarFormat, weekDayFormat, calendarFormattingSettings); timelineGranularityData.createGranularities(calendar, locale, localizationManager); timelineGranularityData.createLabels(); diff --git a/src/timeLineSettingsModel.ts b/src/timeLineSettingsModel.ts index a4b8cc7..ea58ada 100644 --- a/src/timeLineSettingsModel.ts +++ b/src/timeLineSettingsModel.ts @@ -118,6 +118,13 @@ export class CalendarSettingsCard extends Card { public static readonly DefaultMonth: number = 0; public static readonly DefaultDay: number = 1; + treatAsEndOfFiscalYear = new formattingSettings.ToggleSwitch({ + name: "treatAsEndOfFiscalYear", + displayName: "Treat as end of fiscal year", + displayNameKey: "Visual_TreatAsEndOfFiscalYear", + value: true, + }); + month = new formattingSettings.ItemDropdown({ name: "month", displayName: "Month", @@ -141,7 +148,7 @@ export class CalendarSettingsCard extends Card { displayName: string = "Fiscal Year"; displayNameKey: string = "Visual_FiscalYear"; descriptionKey: string = "Visual_FiscalYear_Description"; - slices = [this.month, this.day]; + slices = [this.treatAsEndOfFiscalYear, this.month, this.day]; } class WeekDaySettingsCard extends Card { diff --git a/stringResources/en-US/resources.resjson b/stringResources/en-US/resources.resjson index 1d4c0e2..f915be4 100644 --- a/stringResources/en-US/resources.resjson +++ b/stringResources/en-US/resources.resjson @@ -6,6 +6,7 @@ "Visual_FiscalYearStart": "Fiscal Year Start", "Visual_FiscalYear": "Fiscal Year", "Visual_FiscalYear_Description": "This option have no sense if ISO 8601 standard was picked", + "Visual_TreatAsEndOfFiscalYear": "Treat as end of fiscal year", "Visual_Month": "Month", "Visual_Month_January": "January", "Visual_Month_February": "February", diff --git a/test/visual.test.ts b/test/visual.test.ts index bb8d0fb..4472835 100644 --- a/test/visual.test.ts +++ b/test/visual.test.ts @@ -27,7 +27,7 @@ import {select as d3Select} from "d3-selection"; import powerbiVisualsApi from "powerbi-visuals-api"; import {assertColorsMatch, d3Click, parseColorString, renderTimeout,} from "powerbi-visuals-utils-testutils"; -import {Calendar, CalendarFormat, WeekdayFormat} from "../src/calendars/calendar"; +import {Calendar, CalendarFormat, CalendarFormattingSettings, WeekdayFormat} from "../src/calendars/calendar"; import {ITimelineCursorOverElement, ITimelineData} from "../src/dataInterfaces"; import {ITimelineDatePeriod, ITimelineDatePeriodBase} from "../src/datePeriod/datePeriod"; import {DatePeriodBase} from "../src/datePeriod/datePeriodBase"; @@ -1029,11 +1029,11 @@ describe("Timeline - Granularity - 1 Jan (Regular Calendar)", () => { calendar = createCalendar(); granularities = [ - new YearGranularity(calendar, null, null), - new QuarterGranularity(calendar, null), - new WeekGranularity(calendar, null, null), - new MonthGranularity(calendar, null), - new DayGranularity(calendar, null), + new YearGranularity(calendar, "en-US", null), + new QuarterGranularity(calendar, "en-US"), + new WeekGranularity(calendar, "en-US", null), + new MonthGranularity(calendar, "en-US"), + new DayGranularity(calendar, "en-US"), ]; }); @@ -1075,11 +1075,11 @@ describe("Timeline - Granularity - 1 Apr (Fiscal Calendar)", () => { calendar = createCalendar(3); granularities = [ - new YearGranularity(calendar, null, null), - new QuarterGranularity(calendar, null), - new WeekGranularity(calendar, null, null), - new MonthGranularity(calendar, null), - new DayGranularity(calendar, null), + new YearGranularity(calendar, "en-US", null), + new QuarterGranularity(calendar, "en-US"), + new WeekGranularity(calendar, "en-US", null), + new MonthGranularity(calendar, "en-US"), + new DayGranularity(calendar, "en-US"), ]; }); @@ -1157,7 +1157,8 @@ describe("Timeline - Granularity - ISO 8601 Week numbering", () => { let calendar: Calendar; beforeEach(() => { - calendar = new CalendarISO8061(); + const calendarFormattingSettings: CalendarFormattingSettings = { treatAsEndOfFiscalYear: true }; + calendar = new CalendarISO8061(calendarFormattingSettings); }); describe("ISO Calendar Methods", () => { @@ -1723,6 +1724,7 @@ function createCalendar( day: number = 1, week: number = 1, dayOfWeekSelectionOn: boolean = false, + treatAsEndOfFiscalYear: boolean = true, ): Calendar { const calendarSettings: CalendarFormat = { @@ -1735,7 +1737,7 @@ function createCalendar( daySelection: dayOfWeekSelectionOn, }; - return new Calendar(calendarSettings, weekDaySettings); + return new Calendar(calendarSettings, weekDaySettings, { treatAsEndOfFiscalYear }); } function createDatePeriod(dates: Date[]): ITimelineDatePeriod[] {