vault/ui/app/components/clients/date-range.ts
Vault Automation 5d869440c3
[UI] Ember Data Migration - Client Counts (#12026) (#12132)
* updates flags service to use api service

* converts clients index route to ts

* updates clients config workflows to use api service

* updates clients date-range component to handle Date objects rather than ISO strings

* updates clients page-header component to handle Date objects and use api and capabilities services

* updates clients route to use api and capabilities services

* updates types in client-counts helpers

* updates client counts route to use api service

* updates types for client-counts serializers

* updates date handling in client counts page component

* updates clients overview page component

* converts clients page-header component to ts

* fixes type errors in clients page-header component

* updates client counts tests

* updates client-count-card component to use api service

* converts client-count-card component to ts

* removes model-form-fields test that uses clients/config model

* removes clients/version-history model usage from client-counts helpers tests

* removes migrated models from adapter and model registries

* removes clients ember data models, adapters and serializers

* updates clients date-range component to format dates in time zone

* cleans up references to activityError in client counts route

* adds clients/activity mirage model

* updates activation flags assertions in sync overview tests

* fixes issue selecting current period in clients date-range component and adds test

* fixes issues with enabled state for client counts

* updates parseAPITimestamp to handle date object formatting

* removes unnecesarry type casting for format return in parseAPITimestamp util

* updates parseAPITimestamp to use formatInTimeZone for strings

* updates parseAPITimestamp comment

* updates enabled value in clients config component to boolean

* adds date-fns-tz to core addon

* removes parseISO from date-formatters util in favor of new Date

* updates comments for client counts

* updates retention months validation for client counts config

* updates comment and min retention months default for client counts config

Co-authored-by: Jordan Reimer <zofskeez@gmail.com>
2026-02-03 16:18:52 +00:00

159 lines
5.3 KiB
TypeScript

/**
* Copyright IBM Corp. 2016, 2025
* SPDX-License-Identifier: BUSL-1.1
*/
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { buildISOTimestamp, parseAPITimestamp } from 'core/utils/date-formatters';
import timestamp from 'core/utils/timestamp';
import { format } from 'date-fns';
import type FlagsService from 'vault/services/flags';
import type VersionService from 'vault/services/version';
import type { HTMLElementEvent } from 'forms';
interface OnChangeParams {
start_time: string;
end_time: string;
}
interface Args {
onChange: (callback: OnChangeParams) => void;
setEditModalVisible: (visible: boolean) => void;
showEditModal: boolean;
startTimestamp: Date;
endTimestamp: Date;
billingStartTime: Date;
retentionMonths: number;
}
/**
* @module ClientsDateRange
* ClientsDateRange components are used to display the current date range and provide a modal interface for editing the date range.
*
* @example
*
* <Clients::DateRange @startTimestamp="2018-01-01T14:15:30Z" @endTimestamp="2019-01-31T14:15:30Z" @onChange={{this.handleDateChange}} />
*
* @param {function} onChange - callback when a new range is saved.
* @param {function} setEditModalVisible - callback to tell parent header when modal is opened/closed
* @param {boolean} showEditModal - boolean for when parent header triggers the modal open
* @param {string} [startTimestamp] - ISO string timestamp of the start date for the displayed client count data
* @param {string} [endTimestamp] - ISO string timestamp of the end date for the displayed client count data
* @param {int} [retentionMonths=48] - number of months for historical billing
* @param {string} [billingStartTime] - ISO string timestamp of billing start date
*/
export default class ClientsDateRangeComponent extends Component<Args> {
@service declare readonly flags: FlagsService;
@service declare readonly version: VersionService;
@tracked modalStart = ''; // format yyyy-MM
@tracked modalEnd = ''; // format yyyy-MM
currentMonth = timestamp.now();
previousMonth = format(
new Date(this.currentMonth.getUTCFullYear(), this.currentMonth.getUTCMonth() - 1, 1),
'yyyy-MM'
);
constructor(owner: unknown, args: Args) {
super(owner, args);
this.setTrackedFromArgs();
}
get historicalBillingPeriods() {
// we want whole billing periods
const { billingStartTime } = this.args;
const totalMonths = this.args.retentionMonths || 48;
const count = Math.floor(totalMonths / 12);
const periods: Date[] = [];
for (let i = 1; i <= count; i++) {
const startDate = new Date(billingStartTime);
const utcYear = startDate.getUTCFullYear() - i;
startDate.setUTCFullYear(utcYear);
periods.push(startDate);
}
return periods;
}
get validationError() {
if (!this.modalStart || !this.modalEnd) {
return 'You must supply both start and end dates.';
}
if (this.modalStart > this.modalEnd) {
return 'Start date must be before end date.';
}
const isCurrentMonth = this.modalStart > this.previousMonth || this.modalEnd > this.previousMonth;
if (this.version.isCommunity && isCurrentMonth) {
return 'You cannot select the current month or beyond.';
}
return null;
}
@action onClose() {
// since the component never gets torn down, we have to manually re-set this on close
this.setTrackedFromArgs();
this.args.setEditModalVisible(false);
}
@action updateDate(evt: HTMLElementEvent<HTMLInputElement>) {
const { name, value } = evt.target;
this[name as 'modalStart' | 'modalEnd'] = value;
}
// used for CE date picker
@action handleSave() {
if (this.validationError) return;
const params: OnChangeParams = { start_time: '', end_time: '' };
if (this.modalStart) {
params.start_time = this.formatModalTimestamp(this.modalStart, false);
}
if (this.modalEnd) {
params.end_time = this.formatModalTimestamp(this.modalEnd, true);
}
this.args.onChange(params);
this.onClose();
}
@action
updateEnterpriseDateRange(start: Date, close: CallableFunction) {
// We do not send an end_time so the backend handles computing the expected billing period
const start_time = start ? start.toISOString() : '';
this.args.onChange({ start_time, end_time: '' });
close();
}
// HELPERS
formatModalTimestamp(modalValue: string, isEndDate: boolean) {
const [yearString, month] = modalValue.split('-');
const monthIdx = Number(month) - 1;
const year = Number(yearString);
return buildISOTimestamp({ monthIdx, year, isEndDate });
}
setTrackedFromArgs() {
if (this.args.startTimestamp) {
this.modalStart = this.formatDate(this.args.startTimestamp, 'yyyy-MM');
}
if (this.args.endTimestamp) {
this.modalEnd = this.formatDate(this.args.endTimestamp, 'yyyy-MM');
}
}
// TEMPLATE HELPERS
formatDate = (date: Date, displayFormat = 'MMMM yyyy') => parseAPITimestamp(date, displayFormat);
isSelected = (dropdownTimestamp: Date) => {
// Compare against this.args.startTimestamp because it's from the URL query param
// which is used to query the client count activity API.
const selectedStart = this.formatDate(this.args.startTimestamp);
return this.formatDate(dropdownTimestamp) === selectedStart;
};
}