mirror of
https://github.com/OneUptime/oneuptime
synced 2024-11-21 22:59:07 +00:00
Add new methods and properties to QueryHelper and MonitorStatusTimeline
This commit is contained in:
parent
f64a349e4e
commit
a9bc14e416
@ -0,0 +1,105 @@
|
||||
import DataMigrationBase from './DataMigrationBase';
|
||||
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
|
||||
import Project from 'Model/Models/Project';
|
||||
import ProjectService from 'CommonServer/Services/ProjectService';
|
||||
import Monitor from 'Model/Models/Monitor';
|
||||
import MonitorService from 'CommonServer/Services/MonitorService';
|
||||
import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline';
|
||||
import MonitorStatusTimelineService from 'CommonServer/Services/MonitorStatusTimelineService';
|
||||
import SortOrder from 'Common/Types/BaseDatabase/SortOrder';
|
||||
|
||||
export default class AddEndDateToMonitorStatusTimeline extends DataMigrationBase {
|
||||
public constructor() {
|
||||
super('AddEndDateToMonitorStatusTimeline');
|
||||
}
|
||||
|
||||
public override async migrate(): Promise<void> {
|
||||
// get all the users with email isVerified true.
|
||||
|
||||
const projects: Array<Project> = await ProjectService.findBy({
|
||||
query: {},
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
skip: 0,
|
||||
limit: LIMIT_MAX,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const project of projects) {
|
||||
// add ended scheduled maintenance state for each of these projects.
|
||||
// first fetch resolved state. Ended state order is -1 of resolved state.
|
||||
|
||||
const monitors: Array<Monitor> = await MonitorService.findBy({
|
||||
query: {
|
||||
projectId: project.id!,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
skip: 0,
|
||||
limit: LIMIT_MAX,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const monitor of monitors) {
|
||||
const statusTimelines: Array<MonitorStatusTimeline> =
|
||||
await MonitorStatusTimelineService.findBy({
|
||||
query: {
|
||||
monitorId: monitor.id!,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
createdAt: true,
|
||||
},
|
||||
skip: 0,
|
||||
limit: LIMIT_MAX,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
sort: {
|
||||
createdAt: SortOrder.Ascending,
|
||||
},
|
||||
});
|
||||
|
||||
for (let i: number = 0; i < statusTimelines.length; i++) {
|
||||
const statusTimeline: MonitorStatusTimeline | undefined =
|
||||
statusTimelines[i];
|
||||
|
||||
if (!statusTimeline) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let endDate: Date | null = null;
|
||||
|
||||
if (
|
||||
statusTimelines[i + 1] &&
|
||||
statusTimelines[i + 1]?.createdAt
|
||||
) {
|
||||
endDate = statusTimelines[i + 1]!.createdAt!;
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
await MonitorStatusTimelineService.updateOneById({
|
||||
id: statusTimeline!.id!,
|
||||
data: {
|
||||
endsAt: endDate,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async rollback(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import AddDefaultGlobalConfig from './AddDefaultGlobalConfig';
|
||||
import AddDowntimeMonitorStatusToStatusPage from './AddDowntimeMonitorStatusToStatusPage';
|
||||
import AddEndDateToMonitorStatusTimeline from './AddEndDateToMonitorStatusTimeline';
|
||||
import AddEndedState from './AddEndedState';
|
||||
import AddMonitoringDatesToMonitor from './AddMonitoringDatesToMonitors';
|
||||
import AddOwnerInfoToProjects from './AddOwnerInfoToProject';
|
||||
@ -27,6 +28,7 @@ const DataMigrations: Array<DataMigrationBase> = [
|
||||
new AddPostedAtToPublicNotes(),
|
||||
new MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage(),
|
||||
new AddDowntimeMonitorStatusToStatusPage(),
|
||||
new AddEndDateToMonitorStatusTimeline(),
|
||||
];
|
||||
|
||||
export default DataMigrations;
|
||||
|
@ -79,6 +79,14 @@ export default class OneUptimeDate {
|
||||
return moment(date).fromNow();
|
||||
}
|
||||
|
||||
public static differenceBetweenTwoDatesAsFromattedString(
|
||||
date1: Date,
|
||||
date2: Date
|
||||
): string {
|
||||
const seconds: number = this.getSecondsBetweenTwoDates(date1, date2);
|
||||
return this.secondsToFormattedFriendlyTimeString(seconds);
|
||||
}
|
||||
|
||||
public static toTimeString(date: Date | string): string {
|
||||
if (typeof date === 'string') {
|
||||
date = this.fromString(date);
|
||||
@ -497,34 +505,104 @@ export default class OneUptimeDate {
|
||||
public static secondsToFormattedFriendlyTimeString(
|
||||
seconds: number
|
||||
): string {
|
||||
const startDate: moment.Moment = moment.utc(0);
|
||||
const date: moment.Moment = moment.utc(seconds * 1000);
|
||||
const hours: string = date.format('HH');
|
||||
const mins: string = date.format('mm');
|
||||
const secs: string = date.format('ss');
|
||||
|
||||
let text: string = '';
|
||||
let hasHours: boolean = false;
|
||||
let hasMins: boolean = false;
|
||||
if (hours !== '00') {
|
||||
hasHours = true;
|
||||
text += hours + ' hours ';
|
||||
}
|
||||
// get the difference between the two dates as friendly formatted string
|
||||
|
||||
if (mins !== '00' || hasHours) {
|
||||
hasMins = true;
|
||||
let formattedString: string = '';
|
||||
|
||||
if (hasHours) {
|
||||
text += ', ';
|
||||
// years between two dates
|
||||
const years: number = date.diff(startDate, 'years');
|
||||
|
||||
if (years > 0) {
|
||||
let text: string = 'years ';
|
||||
if (years === 1) {
|
||||
text = 'year ';
|
||||
}
|
||||
|
||||
text += mins + ' minutes ';
|
||||
// add years to start date
|
||||
startDate.add(years, 'years');
|
||||
|
||||
formattedString += years + ' ' + text;
|
||||
}
|
||||
|
||||
if (!(hasHours && hasMins)) {
|
||||
text += secs + ' seconds. ';
|
||||
const months: number = date.diff(startDate, 'months');
|
||||
|
||||
if (months > 0) {
|
||||
let text: string = 'months ';
|
||||
|
||||
if (months === 1) {
|
||||
text = 'month ';
|
||||
}
|
||||
|
||||
// add months to start date
|
||||
startDate.add(months, 'months');
|
||||
|
||||
formattedString += months + ' ' + text;
|
||||
}
|
||||
|
||||
return text;
|
||||
const days: number = date.diff(startDate, 'days');
|
||||
|
||||
if (days > 0) {
|
||||
let text: string = 'days ';
|
||||
|
||||
if (days === 1) {
|
||||
text = 'day ';
|
||||
}
|
||||
|
||||
// add days to start date
|
||||
startDate.add(days, 'days');
|
||||
|
||||
formattedString += days + ' ' + text;
|
||||
}
|
||||
|
||||
const hours: number = date.diff(startDate, 'hours');
|
||||
|
||||
if (hours > 0) {
|
||||
let text: string = 'hours ';
|
||||
|
||||
if (hours === 1) {
|
||||
text = 'hour ';
|
||||
}
|
||||
|
||||
// add hours to start date
|
||||
startDate.add(hours, 'hours');
|
||||
|
||||
formattedString += hours + ' ' + text;
|
||||
}
|
||||
|
||||
const minutes: number = date.diff(startDate, 'minutes');
|
||||
|
||||
if (minutes > 0) {
|
||||
let text: string = 'mins ';
|
||||
|
||||
if (minutes === 1) {
|
||||
text = 'min ';
|
||||
}
|
||||
|
||||
// add minutes to start date
|
||||
startDate.add(minutes, 'minutes');
|
||||
|
||||
formattedString += minutes + ' ' + text;
|
||||
}
|
||||
|
||||
const secondsLeft: number = date.diff(startDate, 'seconds');
|
||||
|
||||
if (secondsLeft > 0) {
|
||||
let text: string = 'secs ';
|
||||
|
||||
if (secondsLeft === 1) {
|
||||
text = 'sec ';
|
||||
}
|
||||
|
||||
// add seconds to start date
|
||||
startDate.add(secondsLeft, 'seconds');
|
||||
|
||||
formattedString += secondsLeft + ' ' + text;
|
||||
}
|
||||
|
||||
return formattedString.trim();
|
||||
}
|
||||
|
||||
public static getGreaterDate(a: Date, b: Date): Date {
|
||||
@ -810,7 +888,7 @@ export default class OneUptimeDate {
|
||||
momentDate.format(formatstring) +
|
||||
' ' +
|
||||
(onlyShowDate ? '' : this.getCurrentTimezoneString())
|
||||
);
|
||||
).trim();
|
||||
}
|
||||
|
||||
public static getDayInSeconds(days?: number | undefined): number {
|
||||
|
@ -736,7 +736,7 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
monitorId: QueryHelper.in(
|
||||
monitorsOnStatusPageForTimeline
|
||||
),
|
||||
createdAt: QueryHelper.inBetween(
|
||||
endsAt: QueryHelper.inBetweenOrNull(
|
||||
startDate,
|
||||
endDate
|
||||
),
|
||||
@ -744,6 +744,7 @@ export default class StatusPageAPI extends BaseAPI<
|
||||
select: {
|
||||
monitorId: true,
|
||||
createdAt: true,
|
||||
endsAt: true,
|
||||
monitorStatus: {
|
||||
name: true,
|
||||
color: true,
|
||||
|
@ -12,6 +12,7 @@ import PositiveNumber from 'Common/Types/PositiveNumber';
|
||||
import CreateBy from '../Types/Database/CreateBy';
|
||||
import UserService from './UserService';
|
||||
import User from 'Model/Models/User';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
|
||||
export class Service extends DatabaseService<MonitorStatusTimeline> {
|
||||
public constructor(postgresDatabase?: PostgresDatabase) {
|
||||
@ -22,6 +23,10 @@ export class Service extends DatabaseService<MonitorStatusTimeline> {
|
||||
protected override async onBeforeCreate(
|
||||
createBy: CreateBy<MonitorStatusTimeline>
|
||||
): Promise<OnCreate<MonitorStatusTimeline>> {
|
||||
if (!createBy.data.monitorId) {
|
||||
throw new BadDataException('monitorId is null');
|
||||
}
|
||||
|
||||
if (
|
||||
(createBy.data.createdByUserId ||
|
||||
createBy.data.createdByUser ||
|
||||
@ -57,7 +62,29 @@ export class Service extends DatabaseService<MonitorStatusTimeline> {
|
||||
}
|
||||
}
|
||||
|
||||
return { createBy, carryForward: null };
|
||||
const lastMonitorStatusTimeline: MonitorStatusTimeline | null =
|
||||
await this.findOneBy({
|
||||
query: {
|
||||
monitorId: createBy.data.monitorId,
|
||||
},
|
||||
sort: {
|
||||
createdAt: SortOrder.Descending,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
createBy,
|
||||
carryForward: {
|
||||
lastMonitorStatusTimelineId:
|
||||
lastMonitorStatusTimeline?.id || null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override async onCreateSuccess(
|
||||
@ -72,6 +99,21 @@ export class Service extends DatabaseService<MonitorStatusTimeline> {
|
||||
throw new BadDataException('monitorStatusId is null');
|
||||
}
|
||||
|
||||
// update the last status as ended.
|
||||
|
||||
if (onCreate.carryForward.lastMonitorStatusTimelineId) {
|
||||
await this.updateOneById({
|
||||
id: onCreate.carryForward.lastMonitorStatusTimelineId!,
|
||||
data: {
|
||||
endsAt:
|
||||
createdItem.createdAt || OneUptimeDate.getCurrentDate(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await MonitorService.updateOneBy({
|
||||
query: {
|
||||
_id: createdItem.monitorId?.toString(),
|
||||
|
@ -241,6 +241,23 @@ export default class QueryHelper {
|
||||
);
|
||||
}
|
||||
|
||||
public static inBetweenOrNull(
|
||||
startValue: number | Date,
|
||||
endValue: number | Date
|
||||
): FindOperator<any> {
|
||||
const rid1: string = Text.generateRandomText(10);
|
||||
const rid2: string = Text.generateRandomText(10);
|
||||
return Raw(
|
||||
(alias: string) => {
|
||||
return `((${alias} >= :${rid1} and ${alias} <= :${rid2})) or (${alias} IS NULL)`;
|
||||
},
|
||||
{
|
||||
[rid1]: startValue,
|
||||
[rid2]: endValue,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static queryJson(value: JSONObject): FindOperator<any> {
|
||||
// seed random text
|
||||
const values: JSONObject = {};
|
||||
|
@ -164,16 +164,23 @@ const DayUptimeGraph: FunctionComponent<ComponentProps> = (
|
||||
|
||||
for (const key in secondsOfEvent) {
|
||||
hasEvents = true;
|
||||
toolTipText += `, ${
|
||||
eventLabels[key]
|
||||
} for ${OneUptimeDate.secondsToFormattedFriendlyTimeString(
|
||||
secondsOfEvent[key] || 0
|
||||
)}`;
|
||||
|
||||
// TODO: Add rules here.
|
||||
|
||||
const eventStatusId: string = key;
|
||||
|
||||
// if this is downtime state then, include tooltip.
|
||||
|
||||
if (
|
||||
(props.downtimeEventStatusIds?.filter((id: ObjectID) => {
|
||||
return id.toString() === eventStatusId.toString();
|
||||
}).length || 0) > 0
|
||||
) {
|
||||
toolTipText += `, ${
|
||||
eventLabels[key]
|
||||
} for ${OneUptimeDate.secondsToFormattedFriendlyTimeString(
|
||||
secondsOfEvent[key] || 0
|
||||
)}`;
|
||||
}
|
||||
|
||||
const isDowntimeEvent: boolean = Boolean(
|
||||
props.downtimeEventStatusIds?.find((id: ObjectID) => {
|
||||
return id.toString() === eventStatusId;
|
||||
|
@ -22,6 +22,7 @@ import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
|
||||
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
|
||||
import Modal, { ModalWidth } from 'CommonUI/src/Components/Modal/Modal';
|
||||
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
|
||||
const StatusTimeline: FunctionComponent<PageComponentProps> = (
|
||||
props: PageComponentProps
|
||||
@ -170,9 +171,35 @@ const StatusTimeline: FunctionComponent<PageComponentProps> = (
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Reported At',
|
||||
title: 'Starts At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
endsAt: true,
|
||||
},
|
||||
title: 'Ends At',
|
||||
type: FieldType.DateTime,
|
||||
noValueMessage: 'Currently Active',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
endsAt: true,
|
||||
},
|
||||
title: 'Duration',
|
||||
type: FieldType.Text,
|
||||
getElement: (item: JSONObject): ReactElement => {
|
||||
return (
|
||||
<p>
|
||||
{OneUptimeDate.differenceBetweenTwoDatesAsFromattedString(
|
||||
item['createdAt'] as Date,
|
||||
(item['endsAt'] as Date) ||
|
||||
OneUptimeDate.getCurrentDate()
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{showViewLogsModal ? (
|
||||
|
@ -472,4 +472,28 @@ export default class MonitorStatusTimeline extends BaseModel {
|
||||
nullable: true,
|
||||
})
|
||||
public rootCause?: string = undefined;
|
||||
|
||||
@Index()
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CanCreateMonitorStatusTimeline,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.CanReadMonitorStatusTimeline,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.Date })
|
||||
@Column({
|
||||
type: ColumnType.Date,
|
||||
nullable: true,
|
||||
unique: false,
|
||||
})
|
||||
public endsAt?: Date = undefined;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user