Repository navigation
TeamDynamix 1.2.0: Team Performance dashboard, custom attributes and 1.1.0 dashboard fixes #172
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f51894b
Add reports and improve TeamDynamix filters
shawn149 72091b3
Add toggle for advanced settings
shawn149 b6519c2
Add TeamDynamix team performance dashboard
shawn149 bc613c7
Clarify stale-ticket insights and query counts
shawn149 7e6f3d2
Add TeamDynamix dashboard screenshots
shawn149 fbef8cb
Clarify configuration item tile limit
shawn149 5941f9b
Ensure report column names are unique
shawn149 a89d6f3
Document TeamDynamix ticket result limits
shawn149 007e1a7
Report failed ticketing app lookups
shawn149 8bf20a9
Clarify CMDB tile 10,000-row limit
shawn149 d1e7aa0
Reuse shared rate-limit handling for tickets
shawn149 48d2b3a
Use toggle for advanced settings
shawn149 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| { | ||
| "name": "report", | ||
| "displayName": "Report", | ||
| "description": "Rows of a Report Builder report, with the report's own columns (including custom attributes)", | ||
| "tags": ["Reports"], | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "get", | ||
| "endpointPath": "api/reports/{{typeof report === 'undefined' || !report ? 0 : Array.isArray(report) ? (report[0] && report[0].value) || 0 : report}}", | ||
| "getArgs": [{ "key": "withData", "value": "true" }], | ||
| "paging": { "mode": "none" }, | ||
| "postRequestScript": "report.js", | ||
| "errorHandling": { | ||
| "type": "script", | ||
| "script": "errorHandling/report.js" | ||
| } | ||
| }, | ||
| "matches": "none", | ||
| "manualConfigApply": true, | ||
| "ui": [ | ||
| { | ||
| "type": "autocomplete", | ||
| "name": "report", | ||
| "label": "Report", | ||
| "help": "Build the report in TeamDynamix Report Builder, adding custom attributes as columns. Only reports the TeamDynamix admin service account can see are listed. A report ID can also be typed in.", | ||
| "allowCustomValues": true, | ||
| "data": { | ||
| "source": "dataStream", | ||
| "dataStreamName": "reports", | ||
| "dataSourceConfig": {} | ||
| }, | ||
| "validation": { "required": true } | ||
| } | ||
| ], | ||
| "metadata": [], | ||
| "timeframes": ["none"], | ||
| "supportsNoneTimeframe": true, | ||
| "defaultTimeframe": "none" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| { | ||
| "name": "reports", | ||
| "displayName": "Reports", | ||
| "description": "Report Builder reports visible to the service account (used to pick a report)", | ||
| "tags": ["Reports"], | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "get", | ||
| "endpointPath": "api/reports", | ||
| "paging": { "mode": "none" }, | ||
| "postRequestScript": "reports.js", | ||
| "errorHandling": { | ||
| "type": "script", | ||
| "script": "errorHandling/rateLimit.js" | ||
| } | ||
| }, | ||
| "matches": "none", | ||
| "metadata": [ | ||
| { "name": "label", "displayName": "Report", "shape": "string", "role": "label" }, | ||
| { "name": "value", "displayName": "Report ID", "shape": "string", "role": "value" }, | ||
| { "name": "sourceName", "displayName": "Source", "shape": "string" }, | ||
| { "name": "appName", "displayName": "Application", "shape": "string" }, | ||
| { "name": "owningGroupName", "displayName": "Owning Group", "shape": "string" } | ||
| ], | ||
| "timeframes": ["none"], | ||
| "supportsNoneTimeframe": true, | ||
| "defaultTimeframe": "none", | ||
| "visibility": { "type": "hidden" } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
plugins/TeamDynamix/v1/dataStreams/scripts/errorHandling/report.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // The Report stream's own error script. rateLimit.js, shared by every other stream, explains a | ||
| // 404 as a tile scoped to the wrong kind of application, which is wrong here: a report that | ||
| // can't be found is one that was deleted or that the admin service account can't see. | ||
| const status = response?.status; | ||
| const header = (name) => { | ||
| const headers = response?.headers || {}; | ||
| return headers[name] || headers[name.toLowerCase()] || headers[name.toUpperCase()]; | ||
| }; | ||
|
|
||
| if (!status) { | ||
| result = | ||
| "TeamDynamix didn't respond. Check the Web API address and that the site is reachable " + | ||
| "from SquaredUp."; | ||
| } else if (status === 404 || status === 403) { | ||
| result = | ||
| "TeamDynamix couldn't open this report. Check it still exists, and that it is shared " + | ||
| "with the TeamDynamix admin service account the data source signs in as."; | ||
| } else if (status === 429) { | ||
| const resetAt = header("X-RateLimit-Reset"); | ||
| result = | ||
| "TeamDynamix rate limit reached" + | ||
| (resetAt ? `. The limit resets at ${resetAt} β this tile will load again after that.` : ". Wait a minute and refresh."); | ||
| } else { | ||
| result = data?.Message || data?.message || `TeamDynamix request failed with HTTP ${status}.`; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| // Search results never include custom attributes, and loading assets one by one is out of reach | ||
| // on a large estate (one request each, under a 30-a-minute rate limit). A Report Builder report | ||
| // can include custom attributes as columns and returns every row in one request, so this stream | ||
| // runs a report the customer has built and passes its columns through. | ||
| // | ||
| // Rows key custom attribute values by attribute ID ("3177"), so columns are renamed to the | ||
| // report's own header text. Two columns can share a header (a report can show the same | ||
| // attribute twice, or an attribute named like a built-in field), so a repeat gets its key | ||
| // appended. A dot would be read as a nested path, so it is replaced. | ||
|
|
||
| // TeamDynamix never sends null for an unset date: it sends 0001-01-01T00:00:00 (.NET's minimum | ||
| // date), which SquaredUp displays as 12/31/1900. Anything before 1901-01-02 is treated as unset. | ||
| const UNSET_DATE_BEFORE = Date.parse("1901-01-02T00:00:00Z"); | ||
| const ISO_DATE = /^\d{4}-\d{2}-\d{2}T/; | ||
| const cell = (value) => | ||
| typeof value === "string" && ISO_DATE.test(value) && Date.parse(value) < UNSET_DATE_BEFORE | ||
| ? null | ||
| : value; | ||
|
|
||
| const columns = Array.isArray(data?.DisplayedColumns) ? data.DisplayedColumns : []; | ||
| const used = new Set(); | ||
| const names = columns.map((column) => { | ||
| const header = String(column.HeaderText || column.ColumnName).replace(/\./g, " ").trim(); | ||
| let name = header; | ||
| for (let n = 1; used.has(name); n++) { | ||
| name = n === 1 ? `${header} (${column.ColumnName})` : `${header} (${column.ColumnName} ${n})`; | ||
| } | ||
| used.add(name); | ||
| // Built-in ID columns (AssetID, TicketIDβ¦) are numbers on the wire, which would be shown | ||
| // with a thousands separator ("42,665"), so they are passed as text. | ||
| return { key: column.ColumnName, name, isId: /ID$/.test(column.ColumnName) }; | ||
| }); | ||
|
|
||
| result = (Array.isArray(data?.DataRows) ? data.DataRows : []).map((row) => | ||
| Object.fromEntries( | ||
| names.map(({ key, name, isId }) => { | ||
| const value = cell(row[key] ?? null); | ||
| return [name, isId && typeof value === "number" ? String(value) : value]; | ||
| }) | ||
| ) | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // Feeds the report picker on the Report stream. Names alone aren't unique (a tenant can hold | ||
| // hundreds of reports, several with the same name in different applications), so the label | ||
| // carries the report's source and application. | ||
| result = (Array.isArray(data) ? data : []) | ||
| .map((report) => ({ | ||
| label: `${report.Name} (${[report.ReportSourceName, report.PlatformAppName].filter(Boolean).join(", ")})`, | ||
| value: String(report.ID), | ||
| sourceName: report.ReportSourceName || "", | ||
| appName: report.PlatformAppName || "", | ||
| owningGroupName: report.OwningGroupName || "" | ||
| })) | ||
| .sort((a, b) => a.label.localeCompare(b.label)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| { | ||
| "name": "teamTickets", | ||
| "displayName": "Team Tickets", | ||
| "description": "Tickets assigned to one or more groups, one row per ticket, searched in the ticketing application the groups work in", | ||
| "tags": ["Tickets", "Service Management"], | ||
| "baseDataSourceName": "httpRequestScoped", | ||
| "config": { | ||
| "httpMethod": "post", | ||
| "endpointPath": "api/team/tickets/search", | ||
| "postBody": { | ||
| "MaxResults": "{{(typeof maxResults !== 'undefined' && maxResults) || dataSource.maxRecords || 2000}}", | ||
| "StatusClassIDs": "{{typeof statusClass === 'undefined' || !statusClass || !statusClass.length ? null : [].concat(statusClass).map(Number)}}", | ||
| "CreatedDateFrom": "{{typeof timeframe === 'undefined' || timeframe.enum === 'none' || ((typeof dateField !== 'undefined' && dateField) || 'Created') !== 'Created' ? null : timeframe.start}}", | ||
| "CreatedDateTo": "{{typeof timeframe === 'undefined' || timeframe.enum === 'none' || ((typeof dateField !== 'undefined' && dateField) || 'Created') !== 'Created' ? null : timeframe.end}}", | ||
| "ModifiedDateFrom": "{{typeof timeframe === 'undefined' || timeframe.enum === 'none' || ((typeof dateField !== 'undefined' && dateField) || 'Created') !== 'Modified' ? null : timeframe.start}}", | ||
| "ModifiedDateTo": "{{typeof timeframe === 'undefined' || timeframe.enum === 'none' || ((typeof dateField !== 'undefined' && dateField) || 'Created') !== 'Modified' ? null : timeframe.end}}", | ||
| "ClosedDateFrom": "{{typeof timeframe === 'undefined' || timeframe.enum === 'none' || ((typeof dateField !== 'undefined' && dateField) || 'Created') !== 'Closed' ? null : timeframe.start}}", | ||
| "ClosedDateTo": "{{typeof timeframe === 'undefined' || timeframe.enum === 'none' || ((typeof dateField !== 'undefined' && dateField) || 'Created') !== 'Closed' ? null : timeframe.end}}", | ||
| "PrimaryResponsibilityGroupIDs": "{{objects.map((o) => Number(o.rawId))}}", | ||
| "SlaViolationStatus": "{{typeof sla !== 'undefined' && sla === 'violated' ? true : null}}" | ||
| }, | ||
| "paging": { "mode": "none" }, | ||
| "postRequestScript": "tickets.js", | ||
| "errorHandling": { | ||
| "type": "script", | ||
| "script": "errorHandling/rateLimit.js" | ||
| } | ||
| }, | ||
| "matches": { | ||
| "sourceType": { "type": "oneOf", "values": ["TeamDynamix Group"] } | ||
| }, | ||
| "manualConfigApply": true, | ||
| "ui": [ | ||
| { | ||
| "type": "switch", | ||
| "name": "dateField", | ||
| "label": "Timeframe applies to", | ||
| "defaultValue": "Created", | ||
| "help": "Which ticket date the selected timeframe filters on. Set the timeframe to **None** to ignore dates entirely and return the current backlog.", | ||
| "options": [ | ||
| { "value": "Created", "label": "Created" }, | ||
| { "value": "Modified", "label": "Last modified" }, | ||
| { "value": "Closed", "label": "Closed" } | ||
| ], | ||
| "tileEditorStep": ["Timeframe"] | ||
| }, | ||
| { | ||
| "type": "choiceChips", | ||
| "name": "statusClass", | ||
| "label": "Status", | ||
| "isMulti": true, | ||
| "help": "Leave empty for all tickets.", | ||
| "options": [ | ||
| { "value": "1", "label": "New" }, | ||
| { "value": "2", "label": "In Process" }, | ||
| { "value": "5", "label": "On Hold" }, | ||
| { "value": "6", "label": "Requested" }, | ||
| { "value": "3", "label": "Completed" }, | ||
| { "value": "4", "label": "Cancelled" } | ||
| ] | ||
| }, | ||
| { | ||
| "type": "switch", | ||
| "name": "sla", | ||
| "label": "SLA", | ||
| "defaultValue": "all", | ||
| "options": [ | ||
| { "value": "all", "label": "All" }, | ||
| { "value": "violated", "label": "Violated only" } | ||
| ] | ||
| }, | ||
| { | ||
| "type": "number", | ||
| "name": "maxResults", | ||
| "label": "Maximum tickets", | ||
| "help": "The TeamDynamix ticket search returns a single unpaged result set, so this is a hard ceiling on how many tickets the tile can see.", | ||
| "validation": { "min": 1, "max": 10000, "valueAsNumber": true } | ||
| } | ||
| ], | ||
| "metadata": [ | ||
| { "name": "title", "displayName": "Title", "shape": "string", "role": "label" }, | ||
| { "name": "ticketId", "displayName": "Ticket ID", "shape": "string", "role": "id" }, | ||
| { "name": "link", "displayName": "Link", "shape": ["url", { "label": "Open in TeamDynamix" }], "role": "link" }, | ||
| { | ||
| "name": "health", | ||
| "displayName": "Health", | ||
| "shape": [ | ||
| "state", | ||
| { | ||
| "map": { | ||
| "error": ["SLA violated"], | ||
| "warning": ["On hold"], | ||
| "success": ["Completed"], | ||
| "unknown": ["Open", "Cancelled"] | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| { "name": "statusName", "displayName": "Status", "shape": "string" }, | ||
| { "name": "statusClassName", "displayName": "Status Class", "shape": "string" }, | ||
| { "name": "priorityName", "displayName": "Priority", "shape": "string" }, | ||
| { "name": "priorityOrder", "displayName": "Priority Order", "shape": "number", "visible": false }, | ||
| { "name": "typeName", "displayName": "Type", "shape": "string" }, | ||
| { "name": "classificationName", "displayName": "Classification", "shape": "string" }, | ||
| { "name": "urgencyName", "displayName": "Urgency", "shape": "string", "visible": false }, | ||
| { "name": "impactName", "displayName": "Impact", "shape": "string", "visible": false }, | ||
| { "name": "accountName", "displayName": "Account", "shape": "string" }, | ||
| { "name": "serviceName", "displayName": "Service", "shape": "string" }, | ||
| { "name": "locationName", "displayName": "Location", "shape": "string" }, | ||
| { "name": "sourceName", "displayName": "Source", "shape": "string" }, | ||
| { "name": "responsibleFullName", "displayName": "Assigned To", "shape": "string" }, | ||
| { "name": "responsibleGroupName", "displayName": "Assigned Group", "shape": "string" }, | ||
| { "name": "requestorName", "displayName": "Requestor", "shape": "string" }, | ||
| { "name": "createdDate", "displayName": "Created", "shape": "date", "role": "timestamp" }, | ||
| { "name": "modifiedDate", "displayName": "Modified", "shape": "date" }, | ||
| { "name": "completedDate", "displayName": "Completed", "shape": "date" }, | ||
| { "name": "respondedDate", "displayName": "Responded", "shape": "date", "visible": false }, | ||
| { "name": "daysOld", "displayName": "Age (days)", "shape": "number" }, | ||
| { "name": "slaName", "displayName": "SLA", "shape": "string" }, | ||
| { "name": "isSlaViolated", "displayName": "SLA Violated", "shape": "boolean" }, | ||
| { "name": "isSlaResolveByViolated", "displayName": "Resolve By Missed", "shape": "boolean", "visible": false }, | ||
| { "name": "resolveByDate", "displayName": "Resolve By", "shape": "date" }, | ||
| { "name": "isOnHold", "displayName": "On Hold", "shape": "boolean" }, | ||
| { "name": "actualMinutes", "displayName": "Actual", "shape": "minutes" }, | ||
| { "name": "appId", "displayName": "App ID", "shape": "string", "visible": false } | ||
| ], | ||
| "defaultShaping": { | ||
| "sort": { "by": [["createdDate", "desc"]] } | ||
| }, | ||
| "timeframes": true, | ||
| "supportsNoneTimeframe": true, | ||
| "defaultTimeframe": "dashboard" | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.