Skip to content

Commit a299382

Browse files
committed
Feature: Add configurable defaults for new tickets
- Add client.new_ticket_template for prefilling new tickets - Support default cc, notify, and due values for tickets - Allow ticket actions to set relative due dates like "3 days" - Add due date presets to ticket creation dialogs and action config - Normalize relative due dates when creating tickets via API/actions - Document ticket_due and the new ticket template config - Reordered "New Ticket" sidebar shortcut.
1 parent 51d1b40 commit a299382

15 files changed

Lines changed: 231 additions & 39 deletions

File tree

docs/actions.md

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -332,34 +332,37 @@ Parameters:
332332
| `ticket_type` | String | Yes | See [Ticket.type](data.md#ticket-type) (e.g., `issue`, `task`, etc.). |
333333
| `ticket_assignees` | Array(String) | Yes | Array of [User.username](data.md#user-username) assignees. |
334334
| `ticket_tags` | Array(String) | Optional | Array of [Tag.id](data.md#tag-id) values. |
335+
| `ticket_due` | String or Number | Optional | Due date for the new ticket. This may be an absolute Unix epoch time, or a relative date delta such as `1 day`, `3 days`, or `1d`. |
335336

336337
Example (job error):
337338

338339
```json
339340
{
340-
"enabled": true,
341-
"condition": "error",
342-
"type": "ticket",
343-
"ticket_type": "issue",
344-
"ticket_assignees": ["oncall"],
345-
"ticket_tags": ["production", "sev2"]
341+
"enabled": true,
342+
"condition": "error",
343+
"type": "ticket",
344+
"ticket_type": "issue",
345+
"ticket_assignees": ["oncall"],
346+
"ticket_tags": ["production", "sev2"],
347+
"ticket_due": "3 days"
346348
}
347349
```
348350

349351
Example (alert cleared):
350352

351353
```json
352354
{
353-
"enabled": true,
354-
"condition": "alert_cleared",
355-
"type": "ticket",
356-
"ticket_type": "task",
357-
"ticket_assignees": ["sre"],
358-
"ticket_tags": ["cleanup"]
355+
"enabled": true,
356+
"condition": "alert_cleared",
357+
"type": "ticket",
358+
"ticket_type": "task",
359+
"ticket_assignees": ["sre"],
360+
"ticket_tags": ["cleanup"],
361+
"ticket_due": "1 day"
359362
}
360363
```
361364

362-
See [Tickets](tickets.md) for more details on tickets.
365+
See [Tickets](tickets.md) for more details on tickets, including the [New Ticket Template](tickets.md#new-ticket-template), which can provide default `cc`, `notify`, and `due` values.
363366

364367
### Plugin
365368

docs/config.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,11 @@ When set to `true`, will show a colored badge on sidebar sections for outdated s
682682

683683
Provides sensible defaults for new events (triggers, limits, actions). Used to prefill the New Event form.
684684

685+
### client.new_ticket_template
686+
<!-- Title: New Ticket Template -->
687+
688+
Provides defaults for creating new tickets. Used to prefill the New Ticket form.
689+
685690
### client.chart_defaults
686691
<!-- Title: Graph Defaults -->
687692

docs/data.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2908,7 +2908,7 @@ Each action has a `type` property which dictates what will happen when the condi
29082908
| `tag` | Add one or more [Tags](tags.md) to the running job or workflow. |
29092909
| `store` | Store data in a storage bucket. Requires `bucket_id` (the [Bucket.id](#bucket-id)), `bucket_sync` (species if files and/or data should be stored), and `bucket_glob` (glob pattern to match on files). |
29102910
| `fetch` | Fetch data from a storage bucket. Requires `bucket_id` (the [Bucket.id](#bucket-id)), `bucket_sync` (species if files and/or data should be fetched), and `bucket_glob` (glob pattern to match on files). |
2911-
| `ticket` | Create a ticket. Requires `ticket_type` (see [Ticket.type](#ticket-type)), `ticket_assignees` (an array of [User.username](#user-username)s), and `ticket_tags` (an array of [Tag.id](#tag-id)s). |
2911+
| `ticket` | Create a ticket. Requires `ticket_type` (see [Ticket.type](#ticket-type)), `ticket_assignees` (an array of [User.username](#user-username)s), and `ticket_tags` (an array of [Tag.id](#tag-id)s). Can also include `ticket_due`, as an absolute Unix epoch time or a relative date delta such as `3 days`. |
29122912
| `plugin` | Invoke a custom Plugin for the action. Requires `plugin_id` (the [Plugin.id](#plugin-id)) and `params` (custom parameters defined by the Plugin). |
29132913

29142914
### Limit

docs/tickets.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,69 @@ Related APIs:
5555
- [delete_ticket](api.md#delete_ticket): Permanently delete a ticket.
5656
- [search_tickets](api.md#search_tickets): Search with pagination and sorting; supports compact mode for grids.
5757

58+
### New Ticket Template
59+
60+
You can set defaults for new tickets using the [client.new_ticket_template](config.md#client-new_ticket_template) configuration object. This is useful when your team wants every new ticket to start with common metadata, a standard due date, or a global notification list.
61+
62+
The template applies to tickets created manually in the UI, and parts of it are also used by tickets created from job and alert actions. Specifically, ticket actions inherit `cc`, `notify`, and `due` from the template, unless the action supplies its own due date.
63+
64+
Here is a simple example:
65+
66+
```json
67+
"new_ticket_template": {
68+
"type": "issue",
69+
"status": "open",
70+
"assignees": ["oncall"],
71+
"cc": ["ops-manager"],
72+
"notify": ["ops-team@company.com"],
73+
"tags": ["production"],
74+
"due": "3 days"
75+
}
76+
```
77+
78+
Common options include:
79+
80+
- `assignees`: Default list of user IDs assigned to manually-created tickets.
81+
- `cc`: Default list of xyOps usernames who should receive ticket update emails.
82+
- `notify`: Default list of custom email addresses that should receive ticket update emails.
83+
- `tags`: Default list of tag IDs applied to manually-created tickets.
84+
- `type`: Default ticket type, such as `issue`, `change`, or `maintenance`.
85+
- `status`: Default ticket status, usually `open` or `draft`.
86+
- `due`: Default due date. This may be an absolute Unix epoch time, or a relative date delta such as `"1 day"`, `"3 days"`, `"1 week"`, or `"1d"`.
87+
88+
For example, to copy an operations manager on all new tickets, set:
89+
90+
```json
91+
"new_ticket_template": {
92+
"cc": ["ops-manager"]
93+
}
94+
```
95+
96+
To send all ticket update emails to an outside mailing list, use `notify`:
97+
98+
```json
99+
"new_ticket_template": {
100+
"notify": ["ops-team@company.com"]
101+
}
102+
```
103+
104+
To make every new ticket due three days after it is created, set:
105+
106+
```json
107+
"new_ticket_template": {
108+
"due": "3 days"
109+
}
110+
```
111+
112+
When a ticket has a due date and remains open past that date, xyOps sends daily overdue reminder emails to ticket assignees. Ticket update emails still go to assignees, `cc` users, and `notify` email addresses.
113+
58114
### Job Action
59115

60116
Jobs can create tickets on start or completion based on outcome or tags. Add a "Create Ticket" action to an event, workflow node, or via category/universal defaults. When fired:
61117

62118
- The ticket body is auto-generated (template: job) with useful context (job details, performance, log excerpt, links).
63119
- Category, tags, and server fields are auto-populated from the job when applicable.
120+
- The ticket can inherit default `cc`, `notify`, and `due` values from `client.new_ticket_template`.
64121
- The new ticket is added to the originating job for traceability.
65122

66123
See [Actions](actions.md) for action configuration.
@@ -71,6 +128,7 @@ Alerts can create tickets when an alert fires (or clears, if desired). Add a "Cr
71128

72129
- The ticket body is auto-generated (template: alert) with server and alert context, links to the alert and server, and optionally active job summaries.
73130
- Server is populated from the firing server; tags can be set from the action.
131+
- The ticket can inherit default `cc`, `notify`, and `due` values from `client.new_ticket_template`.
74132
- The new ticket is added to the alert invocation record.
75133

76134

htdocs/index-dev.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@
5252

5353
<div class="section_title sbs_shortcuts"><i class="ctrl mdi mdi-chevron-down"></i><i class="icon mdi mdi-hand-pointing-right">&nbsp;</i>Shortcuts</div>
5454
<div class="section sbs_shortcuts">
55-
<a href="#Tickets?sub=new" id="tab_NewTicket" class="section_item sbs_ticket_searches"><i class="icon mdi mdi-text-box-plus-outline">&nbsp;</i>New Ticket...</a>
5655
<a href="#Events?sub=new" id="tab_NewEvent" class="section_item sbs_scheduler"><i class="icon mdi mdi-calendar-edit-outline">&nbsp;</i>New Event...</a>
5756
<a href="#Workflows?sub=new" id="tab_NewWorkflow" class="section_item sbs_scheduler"><i class="icon mdi mdi-clipboard-edit-outline">&nbsp;</i>New Workflow...</a>
57+
<a href="#Tickets?sub=new" id="tab_NewTicket" class="section_item sbs_ticket_searches"><i class="icon mdi mdi-text-box-plus-outline">&nbsp;</i>New Ticket...</a>
5858
<a href="#Servers?add=1" id="tab_NewServer" class="section_item sbs_monitoring"><i class="icon mdi mdi-server-plus-outline">&nbsp;</i>Add Server...</a>
5959
</div>
6060

htdocs/js/pages/Alerts.class.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,8 @@ Page.Alerts = class Alerts extends Page.PageUtils {
548548
var btn = ['text-box-plus-outline', "Create Ticket"];
549549
var html = '';
550550

551+
var ticket_template = app.config.new_ticket_template || {};
552+
551553
var nice_server = this.getNiceServerText(alert.server);
552554
var new_subject = `Alert: ${def.title} on ${nice_server}`;
553555

@@ -598,6 +600,18 @@ Page.Alerts = class Alerts extends Page.PageUtils {
598600
})
599601
});
600602

603+
// due
604+
html += this.getFormRow({
605+
id: 'd_nt_due_preset',
606+
content: this.getFormMenuSingle({
607+
id: 'fe_nt_due_preset',
608+
options: config.ui.new_ticket_dues,
609+
value: ticket_template.due || '',
610+
auto_add: true,
611+
// 'data-shrinkwrap': 1
612+
})
613+
});
614+
601615
html += '</div>';
602616
Dialog.confirm( title, html, btn, function(result) {
603617
if (!result) return;
@@ -611,11 +625,11 @@ Page.Alerts = class Alerts extends Page.PageUtils {
611625
status: 'open',
612626
category: '',
613627
assignees: $('#fe_nt_assignees').val(),
614-
cc: [],
615-
notify: [],
628+
cc: ticket_template.cc || [],
629+
notify: ticket_template.notify || [],
616630
events: [],
617631
tags: $('#fe_nt_tags').val(),
618-
due: '',
632+
due: $('#fe_nt_due_preset').val(),
619633
server: alert.server || ''
620634
};
621635
if (!ticket.subject.length) return app.badField('#fe_nt_subject', "Please enter a subject line for the ticket.");
@@ -641,7 +655,7 @@ Page.Alerts = class Alerts extends Page.PageUtils {
641655
}); // Dialog.confirm
642656

643657
MultiSelect.init( $('#fe_nt_assignees, #fe_nt_tags') );
644-
SingleSelect.init( $('#fe_nt_type') );
658+
SingleSelect.init( $('#fe_nt_type, #fe_nt_due_preset') );
645659
Dialog.autoResize();
646660

647661
$('#fe_nt_subject').focus().get(0).setSelectionRange( new_subject.length, new_subject.length );

htdocs/js/pages/Job.class.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,8 @@ Page.Job = class Job extends Page.PageUtils {
767767
var btn = ['text-box-plus-outline', "Create Ticket"];
768768
var html = '';
769769

770+
var ticket_template = app.config.new_ticket_template || {};
771+
770772
var event = job.event ? find_object(app.events, { id: job.event }) : { title: "n/a" };
771773
if (!event) event = { title: "n/a" };
772774

@@ -821,6 +823,18 @@ Page.Job = class Job extends Page.PageUtils {
821823
})
822824
});
823825

826+
// due
827+
html += this.getFormRow({
828+
id: 'd_nt_due_preset',
829+
content: this.getFormMenuSingle({
830+
id: 'fe_nt_due_preset',
831+
options: config.ui.new_ticket_dues,
832+
value: ticket_template.due || '',
833+
auto_add: true,
834+
// 'data-shrinkwrap': 1
835+
})
836+
});
837+
824838
html += '</div>';
825839
Dialog.confirm( title, html, btn, function(result) {
826840
if (!result) return;
@@ -834,11 +848,11 @@ Page.Job = class Job extends Page.PageUtils {
834848
status: 'open',
835849
category: job.category || '',
836850
assignees: $('#fe_nt_assignees').val(),
837-
cc: [],
838-
notify: [],
851+
cc: ticket_template.cc || [],
852+
notify: ticket_template.notify || [],
839853
events: [],
840854
tags: $('#fe_nt_tags').val(),
841-
due: '',
855+
due: $('#fe_nt_due_preset').val(),
842856
server: job.server || ''
843857
};
844858
if (!ticket.subject.length) return app.badField('#fe_nt_subject', "Please enter a subject line for the ticket.");
@@ -864,7 +878,7 @@ Page.Job = class Job extends Page.PageUtils {
864878
}); // Dialog.confirm
865879

866880
MultiSelect.init( $('#fe_nt_assignees, #fe_nt_tags') );
867-
SingleSelect.init( $('#fe_nt_type') );
881+
SingleSelect.init( $('#fe_nt_type, #fe_nt_due_preset') );
868882
Dialog.autoResize();
869883

870884
$('#fe_nt_subject').focus().get(0).setSelectionRange( new_subject.length, new_subject.length );

htdocs/js/pages/PageUtils.class.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,6 +2358,7 @@ Page.PageUtils = class PageUtils extends Page.Base {
23582358
// ticket assignees
23592359
html += this.getFormRow({
23602360
id: 'd_nt_assignees',
2361+
label: 'Ticket Assignees:',
23612362
content: this.getFormMenuMulti({
23622363
id: 'fe_nt_assignees',
23632364
options: app.users.map( function(user) { return { id: user.username, title: user.full_name, icon: user.icon || 'account' }; } ),
@@ -2379,6 +2380,18 @@ Page.PageUtils = class PageUtils extends Page.Base {
23792380
})
23802381
});
23812382

2383+
// ticket due
2384+
html += this.getFormRow({
2385+
id: 'd_nt_due_preset',
2386+
label: 'Ticket Due Date:',
2387+
content: this.getFormMenuSingle({
2388+
id: 'fe_nt_due_preset',
2389+
options: config.ui.new_ticket_dues,
2390+
value: '',
2391+
// 'data-shrinkwrap': 1
2392+
})
2393+
});
2394+
23822395
// tags
23832396
html += this.getFormRow({
23842397
id: 'd_eja_tags',
@@ -2476,6 +2489,7 @@ Page.PageUtils = class PageUtils extends Page.Base {
24762489
action.ticket_type = $('#fe_nt_type').val();
24772490
action.ticket_assignees = $('#fe_nt_assignees').val();
24782491
action.ticket_tags = $('#fe_nt_tags').val();
2492+
action.ticket_due = $('#fe_nt_due_preset').val();
24792493
break;
24802494

24812495
case 'suspend':
@@ -2504,7 +2518,7 @@ Page.PageUtils = class PageUtils extends Page.Base {
25042518
} ); // Dialog.confirm
25052519

25062520
var change_action_type = function(new_type) {
2507-
$('#d_eja_email, #d_eja_users, #d_eja_body, #d_eja_web_hook, #d_eja_web_hook_text, #d_eja_run_job, #d_eja_target_server, #d_eja_clear_alert, #d_eja_event_params, #d_eja_start_delay, #d_eja_channel, #d_eja_bucket, #d_eja_bucket_sync, #d_eja_bucket_glob, #d_nt_type, #d_nt_assignees, #d_nt_tags, #d_eja_tags, #d_eja_suspend_sources, #d_eja_plugin, #d_eja_plugin_params').hide();
2521+
$('#d_eja_email, #d_eja_users, #d_eja_body, #d_eja_web_hook, #d_eja_web_hook_text, #d_eja_run_job, #d_eja_target_server, #d_eja_clear_alert, #d_eja_event_params, #d_eja_start_delay, #d_eja_channel, #d_eja_bucket, #d_eja_bucket_sync, #d_eja_bucket_glob, #d_nt_type, #d_nt_assignees, #d_nt_tags, #d_nt_due_preset, #d_eja_tags, #d_eja_suspend_sources, #d_eja_plugin, #d_eja_plugin_params').hide();
25082522

25092523
switch (new_type) {
25102524
case 'email':
@@ -2544,7 +2558,7 @@ Page.PageUtils = class PageUtils extends Page.Base {
25442558
break;
25452559

25462560
case 'ticket':
2547-
$('#d_nt_type, #d_nt_assignees, #d_nt_tags').show();
2561+
$('#d_nt_type, #d_nt_assignees, #d_nt_tags, #d_nt_due_preset').show();
25482562
break;
25492563

25502564
case 'suspend':
@@ -2595,7 +2609,7 @@ Page.PageUtils = class PageUtils extends Page.Base {
25952609
}); // event change
25962610

25972611
MultiSelect.init( $('#fe_eja_users, #fe_nt_assignees, #fe_nt_tags, #fe_eja_tags, #fe_eja_suspend_sources') );
2598-
SingleSelect.init( $('#fe_eja_condition, #fe_eja_type, #fe_eja_event, #fe_eja_channel, #fe_eja_web_hook, #fe_eja_plugin, #fe_eja_bucket, #fe_eja_bucket_sync, #fe_nt_type') );
2612+
SingleSelect.init( $('#fe_eja_condition, #fe_eja_type, #fe_eja_event, #fe_eja_channel, #fe_eja_web_hook, #fe_eja_plugin, #fe_eja_bucket, #fe_eja_bucket_sync, #fe_nt_type, #fe_nt_due_preset') );
25992613
RelativeTime.init( $('#fe_eja_start_delay') );
26002614

26012615
Dialog.autoResize();

htdocs/js/pages/Tickets.class.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -667,13 +667,10 @@ Page.Tickets = class Tickets extends Page.PageUtils {
667667
html += '</div>';
668668
html += '<div class="box_content">';
669669

670-
this.ticket = {
670+
var ticket = this.ticket = deep_copy_object( merge_objects( {
671671
status: "open",
672-
username: app.username,
673672
assignees: [],
674-
// due: normalize_time( time_now() + (86400 * config.default_ticket_due_days), { hour:0, min:0, sec:0 } ),
675673
due: 0,
676-
id: "",
677674
type: "change",
678675
subject: "",
679676
body: "",
@@ -682,7 +679,19 @@ Page.Tickets = class Tickets extends Page.PageUtils {
682679
cc: [],
683680
notify: [],
684681
tags: []
685-
};
682+
}, app.config.new_ticket_template || {} ) );
683+
684+
ticket.id = '';
685+
ticket.username = app.username;
686+
if (!ticket.due) ticket.due = 0;
687+
688+
if (typeof(ticket.due) == 'string') {
689+
ticket.due = get_seconds_from_text( ticket.due ) || 0;
690+
}
691+
if ((typeof(ticket.due) == 'number') && (ticket.due > 0) && (ticket.due < 315360000)) {
692+
var due_date = this.formatDateTZ(app.epoch + ticket.due, '[yyyy]-[mm]-[dd]', config.tz);
693+
ticket.due = this.parseDateTZ( due_date + ' 00:00:00', config.tz );
694+
}
686695

687696
html += this.get_ticket_edit_html();
688697

0 commit comments

Comments
 (0)