diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9948a76..2fa7725 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,6 +4,7 @@ /.github/* @squaredup/community-moderators # Request review from original author +plugins/AutoTask/* @TimWheeler-SQUP plugins/DattoRMM/* @TimWheeler-SQUP plugins/DigiCert/* @shaswot77 plugins/FantasyPremierLeague/* @TimWheeler-SQUP diff --git a/plugins/AutoTask/v1/configValidation.json b/plugins/AutoTask/v1/configValidation.json new file mode 100644 index 0000000..8d8f70c --- /dev/null +++ b/plugins/AutoTask/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Autotask connection", + "dataStream": { "name": "companies", "timeframe": "none" }, + "success": "Successfully connected to Autotask", + "error": "Cannot connect to Autotask — check your Zone URL, Integration Code, Username and Secret", + "required": true + } + ] +} diff --git a/plugins/AutoTask/v1/custom_types.json b/plugins/AutoTask/v1/custom_types.json new file mode 100644 index 0000000..0aba596 --- /dev/null +++ b/plugins/AutoTask/v1/custom_types.json @@ -0,0 +1,37 @@ +[ + { + "name": "Autotask Company", + "sourceType": "autotask-company", + "icon": "building-2", + "singular": "Company", + "plural": "Companies" + }, + { + "name": "AutotaskContact", + "sourceType": "autotask-contact", + "icon": "user", + "singular": "Contact", + "plural": "Contacts" + }, + { + "name": "AutotaskProject", + "sourceType": "autotask-project", + "icon": "folder-kanban", + "singular": "Project", + "plural": "Projects" + }, + { + "name": "AutotaskResource", + "sourceType": "autotask-resource", + "icon": "hard-hat", + "singular": "Resource", + "plural": "Resources" + }, + { + "name": "AutotaskContract", + "sourceType": "autotask-contract", + "icon": "file-text", + "singular": "Contract", + "plural": "Contracts" + } +] diff --git a/plugins/AutoTask/v1/dataStreams/companies.json b/plugins/AutoTask/v1/dataStreams/companies.json new file mode 100644 index 0000000..1eccdff --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/companies.json @@ -0,0 +1,36 @@ +{ + "name": "companies", + "displayName": "Companies", + "description": "All active companies in AutoTask", + "tags": ["Companies"], + "baseDataSourceName": "httpRequestUnscoped", + "timeframes": false, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/Companies/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"exist\",\"field\":\"id\"}]}", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + }, + "metadata": [ + { "name": "companyName", "displayName": "Company Name" }, + { "name": "isActive", "displayName": "Is Active" }, + { "name": "phone", "displayName": "Phone" }, + { "name": "address1", "displayName": "Address" }, + { "name": "city", "displayName": "City" }, + { "name": "state", "displayName": "State" }, + { "name": "postalCode", "displayName": "Postal Code" }, + { "name": "country", "displayName": "Country" }, + { "name": "createDate", "displayName": "Created Date", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "ownerResourceID", "sourceType": "autotask-resource", "shape": "string", "visible": false }, + { "name": "ownerResourceName", "displayName": "Owner", "sourceId": "ownerResourceID", "objectPropertyPath": "name" }, + { "pattern": ".*" } + ] +} diff --git a/plugins/AutoTask/v1/dataStreams/contacts.json b/plugins/AutoTask/v1/dataStreams/contacts.json new file mode 100644 index 0000000..466f2ea --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/contacts.json @@ -0,0 +1,31 @@ +{ + "name": "contacts", + "displayName": "Contacts", + "baseDataSourceName": "httpRequestUnscoped", + "visibility": { "type": "hidden" }, + "timeframes": false, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/Contacts/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"exist\",\"field\":\"id\"}]}", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string" }, + { + "name": "fullName", + "displayName": "Full Name", + "computed": true, + "valueExpression": "{{ $['firstName'] + ' ' + $['lastName'] }}" + }, + { "pattern": ".*" } + ] +} diff --git a/plugins/AutoTask/v1/dataStreams/contractStatus.json b/plugins/AutoTask/v1/dataStreams/contractStatus.json new file mode 100644 index 0000000..c652a39 --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/contractStatus.json @@ -0,0 +1,55 @@ +{ + "name": "contractStatus", + "displayName": "Contract Status", + "description": "Status and key dates for contracts belonging to the selected company", + "tags": ["Contracts"], + "baseDataSourceName": "httpRequestScopedSingle", + "timeframes": false, + "matches": { + "sourceType": "autotask-company" + }, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/Contracts/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"eq\",\"field\":\"companyID\",\"value\":{{objects[0].companyId}}}]}", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + }, + "metadata": [ + { "name": "sourceId", "displayName": "Object ID", "shape": "string", "visible": false }, + { "name": "id", "displayName": "ID", "sourceType": "autotask-contract", "shape": "string", "visible": false }, + { "name": "contractName", "displayName": "Contract Name" }, + { "name": "companyID", "displayName": "Company ID", "sourceType": "autotask-company", "shape": "string", "visible": false }, + { "name": "companyName", "displayName": "Company", "sourceId": "companyID", "objectPropertyPath": "name" }, + { + "name": "contractTypeName", + "displayName": "Contract Type", + "computed": true, + "valueExpression": "{{ $['contractType'] == 1 ? 'Time and Materials' : $['contractType'] == 3 ? 'Fixed Price' : $['contractType'] == 4 ? 'Block Hours' : $['contractType'] == 6 ? 'Retainer' : $['contractType'] == 7 ? 'Recurring Service' : $['contractType'] == 8 ? 'Incident' : 'Unknown' }}" + }, + { + "name": "statusName", + "displayName": "Status", + "computed": true, + "valueExpression": "{{ $['status'] == 1 ? 'Active' : $['status'] == 2 ? 'Upcoming' : $['status'] == 3 ? 'Cancelled' : $['status'] == 4 ? 'Completed' : $['status'] == 6 ? 'Inactive' : 'Unknown' }}", + "shape": ["state", { + "map": { + "success": ["Active", "Completed"], + "error": ["Cancelled"], + "warning": ["Upcoming"], + "unknown": ["Inactive", "Unknown"] + } + }] + }, + { "name": "startDate", "displayName": "Start Date", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "endDate", "displayName": "End Date", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "pattern": ".*" } + ] +} diff --git a/plugins/AutoTask/v1/dataStreams/contracts.json b/plugins/AutoTask/v1/dataStreams/contracts.json new file mode 100644 index 0000000..fe9ad48 --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/contracts.json @@ -0,0 +1,21 @@ +{ + "name": "contracts", + "displayName": "Contracts", + "baseDataSourceName": "httpRequestUnscoped", + "visibility": { "type": "hidden" }, + "timeframes": false, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/Contracts/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"exist\",\"field\":\"id\"}]}", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + } +} diff --git a/plugins/AutoTask/v1/dataStreams/financialHealth.json b/plugins/AutoTask/v1/dataStreams/financialHealth.json new file mode 100644 index 0000000..83f9bc8 --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/financialHealth.json @@ -0,0 +1,50 @@ +{ + "name": "financialHealth", + "displayName": "Financial Health", + "description": "Estimated revenue, cost, and hours across all contracts", + "tags": ["Finance"], + "baseDataSourceName": "httpRequestUnscoped", + "timeframes": false, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/Contracts/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"exist\",\"field\":\"id\"}]}", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + }, + "metadata": [ + { "name": "id", "displayName": "ID", "visible": false, "shape":"string" }, + { "name": "contractName", "displayName": "Contract Name" }, + { + "name": "contractTypeName", + "displayName": "Contract Type", + "computed": true, + "valueExpression": "{{ $['contractType'] == 1 ? 'Time and Materials' : $['contractType'] == 3 ? 'Fixed Price' : $['contractType'] == 4 ? 'Block Hours' : $['contractType'] == 6 ? 'Retainer' : $['contractType'] == 7 ? 'Recurring Service' : $['contractType'] == 8 ? 'Incident' : 'Unknown' }}" + }, + { "name": "estimatedRevenue", "displayName": "Estimated Revenue", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "estimatedCost", "displayName": "Estimated Cost", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "estimatedHours", "displayName": "Estimated Hours", "shape": ["number", { "decimalPlaces": 2 }] }, + { + "name": "statusName", + "displayName": "Status", + "computed": true, + "valueExpression": "{{ $['status'] == 1 ? 'Active' : $['status'] == 2 ? 'Upcoming' : $['status'] == 3 ? 'Cancelled' : $['status'] == 4 ? 'Completed' : $['status'] == 6 ? 'Inactive' : 'Unknown' }}", + "shape": ["state", { + "map": { + "success": ["Active", "Completed"], + "error": ["Cancelled"], + "warning": ["Upcoming"], + "unknown": ["Inactive", "Unknown"] + } + }] + }, + { "pattern": ".*" } + ] +} diff --git a/plugins/AutoTask/v1/dataStreams/projectStatus.json b/plugins/AutoTask/v1/dataStreams/projectStatus.json new file mode 100644 index 0000000..78a2139 --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/projectStatus.json @@ -0,0 +1,46 @@ +{ + "name": "projectStatus", + "displayName": "Project Status", + "description": "Status, progress, and schedule for all projects", + "tags": ["Projects"], + "baseDataSourceName": "httpRequestUnscoped", + "timeframes": false, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/Projects/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"exist\",\"field\":\"id\"}]}", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string" }, + { "name": "projectName", "displayName": "Project Name" }, + { "name": "status", "displayName": "Status (Raw)", "shape": ["number", { "decimalPlaces": 0 }], "visible": false }, + { + "name": "statusName", + "displayName": "Status", + "computed": true, + "valueExpression": "{{ $['status'] == 1 ? 'Active' : $['status'] == 2 ? 'Inactive' : $['status'] == 3 ? 'Completed' : $['status'] == 5 ? 'On Hold' : 'Unknown' }}", + "shape": ["state", { + "map": { + "success": ["Active", "Completed"], + "warning": ["On Hold"], + "unknown": ["Inactive", "Unknown"] + } + }] + }, + { "name": "completedPercentage", "displayName": "Completed %", "shape": ["number", { "decimalPlaces": 0 }] }, + { "name": "actualHours", "displayName": "Actual Hours", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "estimatedTime", "displayName": "Estimated Hours", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "startDateTime", "displayName": "Start Date", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "endDateTime", "displayName": "End Date", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "pattern": ".*" } + ] +} diff --git a/plugins/AutoTask/v1/dataStreams/projects.json b/plugins/AutoTask/v1/dataStreams/projects.json new file mode 100644 index 0000000..cfe895f --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/projects.json @@ -0,0 +1,21 @@ +{ + "name": "projects", + "displayName": "Projects", + "baseDataSourceName": "httpRequestUnscoped", + "visibility": { "type": "hidden" }, + "timeframes": false, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/Projects/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"exist\",\"field\":\"id\"}]}", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + } +} diff --git a/plugins/AutoTask/v1/dataStreams/resourceUtilisation.json b/plugins/AutoTask/v1/dataStreams/resourceUtilisation.json new file mode 100644 index 0000000..423db20 --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/resourceUtilisation.json @@ -0,0 +1,33 @@ +{ + "name": "resourceUtilisation", + "displayName": "Resource Utilization", + "description": "Time entries logged by resources within the selected timeframe", + "tags": ["Resources"], + "baseDataSourceName": "httpRequestUnscoped", + "timeframes": true, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/TimeEntries/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"gte\",\"field\":\"dateWorked\",\"value\":\"{{timeframe.start}}\"},{\"op\":\"lte\",\"field\":\"dateWorked\",\"value\":\"{{timeframe.end}}\"}]}", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + }, + "metadata": [ + { "name": "id", "displayName": "ID", "visible": false,"shape":"string" }, + { "name": "resourceID", "sourceType": "autotask-resource", "shape": "string", "visible": false }, + { "name": "resourceName", "displayName": "Resource", "sourceId": "resourceID", "objectPropertyPath": "name" }, + { "name": "dateWorked", "displayName": "Date Worked", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "hoursWorked", "displayName": "Hours Worked", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "hoursToBill", "displayName": "Hours to Bill", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "billingCodeID", "displayName": "Billing Code ID" }, + { "name": "ticketID", "displayName": "Ticket ID" }, + { "pattern": ".*" } + ] +} diff --git a/plugins/AutoTask/v1/dataStreams/resources.json b/plugins/AutoTask/v1/dataStreams/resources.json new file mode 100644 index 0000000..3a07fce --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/resources.json @@ -0,0 +1,31 @@ +{ + "name": "resources", + "displayName": "Resources", + "baseDataSourceName": "httpRequestUnscoped", + "visibility": { "type": "hidden" }, + "timeframes": false, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/Resources/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"exist\",\"field\":\"id\"}]}", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string" }, + { + "name": "fullName", + "displayName": "Full Name", + "computed": true, + "valueExpression": "{{ $['firstName'] + ' ' + $['lastName'] }}" + }, + { "pattern": ".*" } + ] +} diff --git a/plugins/AutoTask/v1/dataStreams/scripts/tickets.js b/plugins/AutoTask/v1/dataStreams/scripts/tickets.js new file mode 100644 index 0000000..783cbb8 --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/scripts/tickets.js @@ -0,0 +1,2 @@ +const selectedIds = new Set(context.objects.map(o => String(o.companyId))); +result = (data.items ?? []).filter(ticket => selectedIds.has(String(ticket.companyID))); diff --git a/plugins/AutoTask/v1/dataStreams/surveyResults.json b/plugins/AutoTask/v1/dataStreams/surveyResults.json new file mode 100644 index 0000000..16aa655 --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/surveyResults.json @@ -0,0 +1,26 @@ +{ + "name": "surveyResults", + "displayName": "Survey Results", + "description": "Survey results submitted within the selected timeframe", + "tags": ["Surveys"], + "baseDataSourceName": "httpRequestUnscoped", + "timeframes": true, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/SurveyResults/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"gte\",\"field\":\"completeDate\",\"value\":\"{{timeframe.start}}\"},{\"op\":\"lte\",\"field\":\"completeDate\",\"value\":\"{{timeframe.end}}\"}]}", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string", "visible": false }, + { "pattern": ".*" } + ] +} diff --git a/plugins/AutoTask/v1/dataStreams/tickets.json b/plugins/AutoTask/v1/dataStreams/tickets.json new file mode 100644 index 0000000..17766c6 --- /dev/null +++ b/plugins/AutoTask/v1/dataStreams/tickets.json @@ -0,0 +1,177 @@ +{ + "name": "tickets", + "displayName": "Tickets", + "description": "Tickets with last activity within the selected timeframe", + "tags": ["Tickets"], + "baseDataSourceName": "httpRequestScoped", + "timeframes": true, + "matches": { + "sourceType": "autotask-company" + }, + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "atservicesrest/v1.0/Tickets/query?search={\"MaxRecords\":500,\"filter\":[{\"op\":\"in\",\"field\":\"status\",\"value\":[{{statusFilter}}]},{\"op\":\"in\",\"field\":\"priority\",\"value\":[{{priorityFilter}}]},{\"op\":\"gte\",\"field\":\"lastActivityDate\",\"value\":\"{{timeframe.start}}\"},{\"op\":\"lte\",\"field\":\"lastActivityDate\",\"value\":\"{{timeframe.end}}\"}]}", + "postRequestScript": "tickets.js", + "pathToData": "items", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "none" }, + "in": { + "realm": "payload", + "path": "pageDetails.nextPageUrl" + } + } + }, + "metadata": [ + { "name": "sourceId", "displayName": "Object ID", "shape": "string", "visible": false }, + { "name": "id", "displayName": "Ticket ID", "shape": "string", "visible": false }, + { "name": "title", "displayName": "Title" }, + { "name": "description", "displayName": "Description", "visible": false }, + { + "name": "statusName", + "displayName": "Status", + "visible": true, + "computed": true, + "valueExpression": "{{ $['status'] == 1 ? 'New' : $['status'] == 5 ? 'Complete' : $['status'] == 8 ? 'In Progress' : $['status'] == 9 ? 'Waiting Customer' : $['status'] == 10 ? 'Waiting Materials' : $['status'] == 11 ? 'Waiting Vendor' : $['status'] == 12 ? 'Escalate' : $['status'] == 13 ? 'Waiting Approval' : 'Unknown' }}", + "shape": ["state", { + "map": { + "success": ["Complete"], + "error": ["Escalate"], + "warning": ["New", "In Progress"], + "unknown": ["Waiting Customer", "Waiting Materials", "Waiting Vendor", "Waiting Approval", "Unknown"] + } + }] + }, + { + "name": "priorityName", + "displayName": "Priority", + "computed": true, + "valueExpression": "{{ $['priority'] == 1 ? 'Critical' : $['priority'] == 2 ? 'High' : $['priority'] == 3 ? 'Medium' : $['priority'] == 4 ? 'Low' : 'Unknown' }}" + }, + { "name": "priority", "displayName": "Priority (Raw)", "shape": ["number", { "decimalPlaces": 0 }], "visible": false }, + { "name": "companyID", "sourceType": "autotask-company", "shape": "string", "visible": false }, + { "name": "companyName", "displayName": "Company", "sourceId": "companyID", "objectPropertyPath": "name" }, + { "name": "contactID", "sourceType": "autotask-contact", "shape": "string", "visible": false }, + { "name": "contactName", "displayName": "Contact", "sourceId": "contactID", "objectPropertyPath": "name" }, + { "name": "contractID", "sourceType": "autotask-contract", "shape": "string", "visible": false }, + { "name": "contractName", "displayName": "Contract", "sourceId": "contractID", "objectPropertyPath": "name" }, + { "name": "assignedResourceID", "sourceType": "autotask-resource", "shape": "string", "visible": false }, + { "name": "assignedResourceName", "displayName": "Assigned Resource", "sourceId": "assignedResourceID", "objectPropertyPath": "name" }, + { "name": "completedByResourceID", "sourceType": "autotask-resource", "shape": "string", "visible": false }, + { "name": "completedByResourceName", "displayName": "Completed By", "sourceId": "completedByResourceID", "objectPropertyPath": "name" }, + { "name": "firstresponeassignedresourceID", "displayName": "First Response Assigned Resource ID", "sourceType": "autotask-resource", "shape": "string", "visible": false }, + { "name": "firstResponseAssignedResourceName", "displayName": "First Response Resource", "sourceId": "firstresponeassignedresourceID", "objectPropertyPath": "name" }, + { "name": "lastActivityResourceID", "sourceType": "autotask-resource", "shape": "string", "visible": false }, + { "name": "lastActivityResourceName", "displayName": "Last Activity Resource", "sourceId": "LastActivityResourceID", "objectPropertyPath": "name" }, + { "name": "issueType", "displayName": "Issue Type" }, + { "name": "resolution", "displayName": "Resolution" }, + { "name": "creatorResourceID", "sourceType": "autotask-resource", "shape": "string", "visible": false }, + { "name": "creatorResourceName", "displayName": "Creator", "sourceId": "CreatorResourceID", "objectPropertyPath": "name" }, + { "name": "lastActivityDate", "displayName": "Last Activity Date", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "dueDateTime", "displayName": "Due Date", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "apiVendorID", "displayName": "API Vendor ID", "shape": "string", "visible": false }, + { "name": "assignedResourceRoleID", "displayName": "Assigned Resource Role ID", "shape": "string", "visible": false }, + { "name": "billingCodeID", "displayName": "Billing Code ID", "shape": "string", "visible": false }, + { "name": "companyLocationID", "displayName": "Company Location ID", "shape": "string", "visible": false }, + { "name": "configurationItemID", "displayName": "Configuration Item ID", "shape": "string", "visible": false }, + { "name": "contractServiceBundleID", "displayName": "Contract Service Bundle ID", "shape": "string", "visible": false }, + { "name": "contractServiceID", "displayName": "Contract Service ID", "shape": "string", "visible": false }, + { "name": "createdByContactID", "displayName": "Created By Contact ID", "shape": "string", "visible": false }, + { "name": "externalID", "displayName": "External ID", "shape": "string", "visible": false }, + { "name": "firstResponseInitiatingResourceID", "displayName": "First Response Initiating Resource ID", "shape": "string", "visible": false }, + { "name": "impersonatorCreatorResourceID", "displayName": "Impersonator Creator Resource ID", "shape": "string", "visible": false }, + { "name": "monitorID", "displayName": "Monitor ID", "shape": "string", "visible": false }, + { "name": "monitorTypeID", "displayName": "Monitor Type ID", "shape": "string", "visible": false }, + { "name": "opportunityID", "displayName": "Opportunity ID", "shape": "string", "visible": false }, + { "name": "organizationalLevelAssociationID", "displayName": "Organizational Level Association ID", "shape": "string", "visible": false }, + { "name": "problemTicketId", "displayName": "Problem Ticket ID", "shape": "string", "visible": false }, + { "name": "projectID", "displayName": "Project ID", "shape": "string", "visible": false }, + { "name": "queueID", "displayName": "Queue ID", "shape": "string", "visible": false }, + { "name": "rmmAlertID", "displayName": "RMM Alert ID", "shape": "string", "visible": false }, + { "name": "serviceLevelAgreementID", "displayName": "SLA ID", "shape": "string", "visible": false }, + { "name": "completedDate", "displayName": "Completed Date", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "createDate", "displayName": "Created Date", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "firstResponseDateTime", "displayName": "First Response Date Time", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "firstResponseDueDateTime", "displayName": "First Response Due Date Time", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "lastCustomerNotificationDateTime", "displayName": "Last Customer Notification Date Time", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "lastCustomerVisibleActivityDateTime", "displayName": "Last Customer Visible Activity Date Time", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "lastTrackedModificationDateTime", "displayName": "Last Tracked Modification Date Time", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "resolutionPlanDateTime", "displayName": "Resolution Plan Date Time", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "resolutionPlanDueDateTime", "displayName": "Resolution Plan Due Date Time", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "resolvedDateTime", "displayName": "Resolved Date Time", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "resolvedDueDateTime", "displayName": "Resolved Due Date Time", "shape": ["date", { "timeZone": "Etc/UTC" }] }, + { "name": "estimatedHours", "displayName": "Estimated Hours", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "hoursToBeScheduled", "displayName": "Hours To Be Scheduled", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "serviceLevelAgreementPausedNextEventHours", "displayName": "SLA Paused Next Event Hours", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "changeApprovalBoard", "displayName": "Change Approval Board" , "visible": false }, + { "name": "changeApprovalStatus", "displayName": "Change Approval Status" , "visible": false }, + { "name": "changeApprovalType", "displayName": "Change Approval Type" , "visible": false }, + { "name": "changeInfoField1", "displayName": "Change Info Field 1", "visible": false }, + { "name": "changeInfoField2", "displayName": "Change Info Field 2", "visible": false }, + { "name": "changeInfoField3", "displayName": "Change Info Field 3", "visible": false }, + { "name": "changeInfoField4", "displayName": "Change Info Field 4", "visible": false }, + { "name": "changeInfoField5", "displayName": "Change Info Field 5", "visible": false }, + { "name": "creatorType", "displayName": "Creator Type" }, + { "name": "currentServiceThermometerRating", "displayName": "Current Service Thermometer Rating" }, + { "name": "isAssignedToComanaged", "displayName": "Is Assigned To Co-Managed", "visible": false }, + { "name": "isVisibleToComanaged", "displayName": "Is Visible To Co-Managed", "visible": false }, + { "name": "lastActivityPersonType", "displayName": "Last Activity Person Type", "visible": false }, + { "name": "previousServiceThermometerRating", "displayName": "Previous Service Thermometer Rating", "visible": false }, + { "name": "purchaseOrderNumber", "displayName": "Purchase Order Number" }, + { "name": "rmaStatus", "displayName": "RMA Status" }, + { "name": "rmaType", "displayName": "RMA Type" }, + { "name": "serviceLevelAgreementHasBeenMet", "displayName": "SLA Has Been Met" }, + { "name": "serviceThermometerTemperature", "displayName": "Service Thermometer Temperature", "visible": false }, + { "name": "source", "displayName": "Source" , "visible": false}, + { "name": "subIssueType", "displayName": "Sub Issue Type", "visible": false }, + { "name": "ticketCategory", "displayName": "Ticket Category" }, + { "name": "ticketNumber", "displayName": "Ticket Number" }, + { "name": "ticketType", "displayName": "Ticket Type" }, + { "pattern": ".*" , "visible": false } + ], + "ui": [ + { + "name": "statusFilter", + "type": "autocomplete", + "label": "Status", + "allowCustomValues": true, + "isMulti": true, + "defaultValue": "1,5,8,9,10,11,12,13", + "data": { + "source": "fixed", + "values": [ + { "value": "1,5,8,9,10,11,12,13", "label": "All Statuses" }, + { "value": "1", "label": "New" }, + { "value": "8", "label": "In Progress" }, + { "value": "9", "label": "Waiting Customer" }, + { "value": "10", "label": "Waiting Materials" }, + { "value": "11", "label": "Waiting Vendor" }, + { "value": "13", "label": "Waiting Approval" }, + { "value": "12", "label": "Escalate" }, + { "value": "5", "label": "Complete" } + ] + }, + "validation": { "required": true } + }, + { + "name": "priorityFilter", + "type": "autocomplete", + "label": "Priority", + "allowCustomValues": true, + "isMulti": true, + "defaultValue": "1,2,3,4", + "data": { + "source": "fixed", + "values": [ + { "value": "1,2,3,4", "label": "All Priorities" }, + { "value": "1", "label": "Critical" }, + { "value": "2", "label": "High" }, + { "value": "3", "label": "Medium" }, + { "value": "4", "label": "Low" } + ] + }, + "validation": { "required": true } + } + ] +} diff --git a/plugins/AutoTask/v1/defaultContent/manifest.json b/plugins/AutoTask/v1/defaultContent/manifest.json new file mode 100644 index 0000000..bd848d1 --- /dev/null +++ b/plugins/AutoTask/v1/defaultContent/manifest.json @@ -0,0 +1,8 @@ +{ + "items": [ + { + "name": "tickets", + "type": "dashboard" + } + ] +} diff --git a/plugins/AutoTask/v1/defaultContent/tickets.dash.json b/plugins/AutoTask/v1/defaultContent/tickets.dash.json new file mode 100644 index 0000000..26f271a --- /dev/null +++ b/plugins/AutoTask/v1/defaultContent/tickets.dash.json @@ -0,0 +1,414 @@ +{ + "name": "Tickets Overview", + "schemaVersion": "1.4", + "dashboard": { + "_type": "layout/grid", + "contents": [ + { + "static": false, + "w": 1, + "moved": false, + "h": 1, + "x": 0, + "y": 0, + "i": "35674a0b-dd87-4845-a083-33fac61b20a8", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "tickets", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "dueDateTime", + "tenseUnit": "before", + "unit": "minutes", + "operation": "datemorethan", + "value": "1" + }, + { + "column": "statusName", + "operation": "notequals", + "value": "success" + } + ] + }, + "id": "{{dataStreams.tickets}}" + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "_type": "tile/data-stream", + "description": "", + "monitor": { + "tileRollsUp": true, + "monitorType": "threshold", + "condition": { + "columns": [], + "logic": { + "if": [ + { + ">": [ + { + "var": "count" + }, + 0 + ] + }, + "error" + ] + } + }, + "_type": "simple", + "aggregation": "count", + "groupBy": "__group_by_none__", + "frequency": 720 + }, + "activePluginConfigIds": [ + "{{configId}}" + ], + "title": "Overdue Tickets", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { + "type": "count" + }, + "comparisonColumn": "none" + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 1, + "x": 1, + "y": 0, + "i": "99af9bf5-ac61-4546-a13f-d2cd838fd3b6", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "tickets", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "status", + "operation": "notequals", + "value": "5" + } + ] + }, + "id": "{{dataStreams.tickets}}" + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "title": "Open Tickets", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { + "type": "count" + }, + "comparisonColumn": "none" + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 1, + "x": 2, + "y": 0, + "i": "353ede63-eccf-433d-91c0-5f6a2dc33af5", + "z": 0, + "config": { + "dataStream": { + "dataSourceConfig": { + "version": "2.0", + "tables": [ + { + "config": { + "timeframe": "last7days", + "activePluginConfigIds": [ + "{{configId}}" + ], + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "dataStream": { + "name": "tickets", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "statusName", + "operation": "equals", + "value": "success" + } + ] + }, + "id": "{{dataStreams.tickets}}" + } + }, + "tableName": "dataset1" + } + ], + "sql": "SELECT\r\n ROUND( AVG(DATE_DIFF('hour', \"createDate\",\"firstResponseDateTime\")),2) AS \"avg_hours_difference\"\r\nFROM\r\n \"dataset1\"" + }, + "id": "datastream-sql" + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "title": "Avg First Response Time", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "avg_hours_difference", + "comparisonColumn": "none", + "label": "Hours" + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 1, + "x": 3, + "y": 0, + "i": "113d7b96-e608-4dca-82f8-fa93ff880528", + "z": 0, + "config": { + "dataStream": { + "dataSourceConfig": { + "version": "2.0", + "tables": [ + { + "config": { + "timeframe": "last7days", + "activePluginConfigIds": [ + "{{configId}}" + ], + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "dataStream": { + "name": "tickets", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "statusName", + "operation": "equals", + "value": "success" + } + ] + }, + "id": "{{dataStreams.tickets}}" + } + }, + "tableName": "dataset1" + } + ], + "sql": "WITH\r\n \"resolution_times\" AS (\r\n SELECT\r\n CAST(\r\n EXTRACT (\r\n EPOCH\r\n FROM\r\n \"resolvedDateTime\" - \"createDate\"\r\n ) AS INTEGER\r\n ) / 3600 AS \"resolution_time_hours\"\r\n FROM\r\n \"dataset1\"\r\n )\r\nSELECT\r\n ROUND(AVG(\"resolution_time_hours\"),2) AS \"avg_resolution_hours\"\r\nFROM\r\n \"resolution_times\"" + }, + "id": "datastream-sql" + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "title": "Avg Time to Resolution", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "comparisonColumn": "none", + "label": "Hours", + "value": "avg_resolution_hours" + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 3, + "x": 0, + "y": 1, + "i": "73f1150f-0a49-49be-974f-f2da0e33a0b5", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "tickets", + "dataSourceConfig": { + "priorityFilter": "1,2,3,4", + "statusFilter": "1,5,8,9,10,11,12,13" + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "completedDate", + "operation": "empty" + } + ] + }, + "id": "{{dataStreams.tickets}}", + "group": { + "by": [ + [ + "assignedResourceName", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).or(__.has(\"sourceType\", \"autotask-company\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "_type": "tile/data-stream", + "description": "Open tickets", + "activePluginConfigIds": [ + "{{configId}}" + ], + "title": "Tickets per Assigned Resource", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "assignedResourceName_uniqueValues", + "count" + ], + "hiddenColumns": [] + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 3, + "x": 2, + "y": 1, + "i": "15c45143-76f4-4d64-93e1-5e270dd78396", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "tickets", + "dataSourceConfig": { + "priorityFilter": "1,2,3,4", + "statusFilter": "1,5,8,9,10,11,12,13" + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "completedDate", + "operation": "notempty" + } + ] + }, + "id": "{{dataStreams.tickets}}", + "group": { + "by": [ + [ + "assignedResourceName", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).or(__.has(\"sourceType\", \"autotask-company\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "title": "Tickets Closed per Resource", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "assignedResourceName_uniqueValues", + "count" + ], + "hiddenColumns": [] + } + } + } + } + } + ], + "version": 83, + "columns": 4 + }, + "path": "tickets", + "folderPath": [] +} diff --git a/plugins/AutoTask/v1/docs/README.md b/plugins/AutoTask/v1/docs/README.md new file mode 100644 index 0000000..47c08b7 --- /dev/null +++ b/plugins/AutoTask/v1/docs/README.md @@ -0,0 +1,32 @@ +# Before you start + +You will need an Autotask account with admin access to create an API User. API Users are a dedicated account type in Autotask used for system integrations — they are separate from regular user accounts and do not consume a standard user licence. + +## Creating an API User + +1. Log in to Autotask and go to **Admin > Resources/Users (HR) > Resources** +2. Click **New** and select **API User** as the resource type +3. Complete the required fields, including an email address — this becomes the **API Username** +4. Under the **Credentials** section, click **Generate** next to **Integration Code** to create your **API Integration Code** +5. Set a **Password / Secret** — this becomes the **API Secret** +6. Save the record + +The API User must have sufficient security permissions to read the entities you want to monitor (Companies, Tickets, Contracts, Projects, Resources, and Survey Results). + +## Finding your Zone URL + +Your Zone URL is based on the Autotask data centre your account is hosted on. To find it: + +1. Log in to Autotask and look at the URL in your browser address bar — for example `https://ww14.autotask.net` +2. Replace `ww` with `webservices` to get the API base URL — for example `https://webservices14.autotask.net` + +Enter the full base including `https://`. + +## Credential field reference + +| Field | Where to find it | +|---|---| +| **Zone URL** | Derived from your Autotask login URL — see above | +| **API Integration Code** | Generated on the API User record under Credentials > Integration Code | +| **API Username** | The email address entered when creating the API User | +| **API Secret** | The password set on the API User record under Credentials | diff --git a/plugins/AutoTask/v1/icon.png b/plugins/AutoTask/v1/icon.png new file mode 100644 index 0000000..c64f438 Binary files /dev/null and b/plugins/AutoTask/v1/icon.png differ diff --git a/plugins/AutoTask/v1/indexDefinitions/default.json b/plugins/AutoTask/v1/indexDefinitions/default.json new file mode 100644 index 0000000..d29a6bf --- /dev/null +++ b/plugins/AutoTask/v1/indexDefinitions/default.json @@ -0,0 +1,88 @@ +{ + "steps": [ + { + "name": "Companies", + "dataStream": { "name": "companies" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "companyName", + "type": { "value": "autotask-company" }, + "properties": [ + "companyType", + "phone", + "lastActivityDate", + "isActive", + { "companyId": "id" } + ] + } + }, + { + "name": "Contacts", + "dataStream": { "name": "contacts" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "fullName", + "type": { "value": "autotask-contact" }, + "properties": [ + "firstName", + "lastName", + "emailAddress", + "phone", + "companyID", + "title" + ] + } + }, + { + "name": "Projects", + "dataStream": { "name": "projects" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "projectName", + "type": { "value": "autotask-project" }, + "properties": [ + "status", + "completedPercentage", + "startDateTime", + "endDateTime" + ] + } + }, + { + "name": "Resources", + "dataStream": { "name": "resources" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "fullName", + "type": { "value": "autotask-resource" }, + "properties": [ + "firstName", + "lastName", + "email", + "isActive", + "licenseType" + ] + } + }, + { + "name": "Contracts", + "dataStream": { "name": "contracts" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "contractName", + "type": { "value": "autotask-contract" }, + "properties": [ + "contractType", + "status", + "startDate", + "endDate" + ] + } + } + ] +} diff --git a/plugins/AutoTask/v1/metadata.json b/plugins/AutoTask/v1/metadata.json new file mode 100644 index 0000000..03e059c --- /dev/null +++ b/plugins/AutoTask/v1/metadata.json @@ -0,0 +1,52 @@ +{ + "name": "autotask", + "displayName": "Autotask", + "version": "1.0.1", + "author": { + "name": "@TimWheeler-SQUP", + "type": "community" + }, + "description": "Monitor tickets, contracts, projects, resources, and company data from Datto Autotask PSA.", + "category": "Service Management", + "type": "cloud", + "schemaVersion": "2.0", + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "authMode": "none", + "headers": [ + { + "value": "{{integrationCode}}", + "key": "ApiIntegrationCode" + }, + { + "value": "application/json", + "key": "ContentType" + }, + { + "value": "{{userName}}", + "key": "UserName" + }, + { + "value": "{{secret}}", + "key": "Secret" + } + ], + "baseUrl": "{{zoneUrl}}" + } + }, + "keywords": ["autotask", "tickets", "psa", "itsm", "contracts", "projects","msp"], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/AutoTask/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/AutoTask", + "label": "Repository" + } + ] +} diff --git a/plugins/AutoTask/v1/ui.json b/plugins/AutoTask/v1/ui.json new file mode 100644 index 0000000..c9d8eb4 --- /dev/null +++ b/plugins/AutoTask/v1/ui.json @@ -0,0 +1,41 @@ +[ + { + "type": "text", + "name": "zoneUrl", + "label": "Zone URL", + "help": "Found in the address bar when logged in to Autotask — e.g. if your login URL is `https://ww14.autotask.net` enter `https://webservices14.autotask.net`", + "placeholder": "https://webservices16.autotask.net", + "validation": { + "required": true, + "pattern": "^.*[^/]$" + } + }, + { + "type": "text", + "name": "userName", + "label": "API Username", + "placeholder": "apiuser@yourdomain.com", + "help": "Email address of the Autotask API User", + "validation": { + "required": true + } + }, + { + "type": "password", + "name": "integrationCode", + "label": "API Integration Code", + "help": "Generated when creating an API User in Autotask — Admin > Resources > API User", + "validation": { + "required": true + } + }, + { + "type": "password", + "name": "secret", + "label": "API Secret", + "help": "Generated when creating an API User in Autotask", + "validation": { + "required": true + } + } +]