Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
88c4108
New AutoTask plugin and data streams
TimWheeler-SQUP Apr 25, 2026
b2687e6
Update folder
TimWheeler-SQUP Apr 27, 2026
878e49e
Fix paging
TimWheeler-SQUP Apr 28, 2026
dc0265b
update icon
TimWheeler-SQUP Apr 29, 2026
46bc12b
remove js file and updated status field
TimWheeler-SQUP Apr 30, 2026
eb642ce
new data stream and removes .js ref
TimWheeler-SQUP Apr 30, 2026
15f856f
New AutoTask plugin and data streams
TimWheeler-SQUP Apr 25, 2026
9722709
Update folder
TimWheeler-SQUP Apr 27, 2026
a9f1a7b
Fix paging
TimWheeler-SQUP Apr 28, 2026
7f86a27
update icon
TimWheeler-SQUP Apr 29, 2026
0fa1dd4
remove js file and updated status field
TimWheeler-SQUP Apr 30, 2026
cae7a44
new data stream and removes .js ref
TimWheeler-SQUP Apr 30, 2026
01c069d
Merge branch 'work/tw/AutoTask' of https://github.com/squaredup/plugi…
TimWheeler-SQUP May 1, 2026
aa930bc
update tags and timeframes
TimWheeler-SQUP May 2, 2026
c20b366
Add OOB dashboard
TimWheeler-SQUP May 3, 2026
e4b0b44
Fix survey results and update contractStatus data streams
TimWheeler-SQUP May 4, 2026
ca0bc31
Improve column mappings and status values. Fix OOB dashboard
TimWheeler-SQUP May 5, 2026
9c26e59
updated plugin type and fixed contractStatus
TimWheeler-SQUP May 6, 2026
4b7ece8
updated terminology and added object and parameter step to Tickets
TimWheeler-SQUP May 9, 2026
0325310
fix OOB dashboards and reduce API calls for high number of objects
TimWheeler-SQUP May 9, 2026
ce3ec2a
Fix oob dashboard
TimWheeler-SQUP May 9, 2026
b73c422
Apply review feedback and remove non-plugin files from PR
TimWheeler-SQUP May 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions plugins/AutoTask/v1/configValidation.json
Original file line number Diff line number Diff line change
@@ -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
}
]
}
37 changes: 37 additions & 0 deletions plugins/AutoTask/v1/custom_types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"name": "Autotask Company",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the name is used for much, but this has a space in while the others do not...

"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"
}
]
36 changes: 36 additions & 0 deletions plugins/AutoTask/v1/dataStreams/companies.json
Original file line number Diff line number Diff line change
@@ -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\"}]}",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given you have paging added, you could reduce the 500 needed here if you hit size limits - or it could be faster fetching more smaller pages.

"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": ".*" }
]
}
31 changes: 31 additions & 0 deletions plugins/AutoTask/v1/dataStreams/contacts.json
Original file line number Diff line number Diff line change
@@ -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": ".*" }
]
}
55 changes: 55 additions & 0 deletions plugins/AutoTask/v1/dataStreams/contractStatus.json
Original file line number Diff line number Diff line change
@@ -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' }}",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using a value expression AND a map here so users can both see our statues like 'success', 'error' as well as nicer raw names like 'Active' and 'Completed'? I think it's OK, just not seen this before.

"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": ".*" }
]
}
21 changes: 21 additions & 0 deletions plugins/AutoTask/v1/dataStreams/contracts.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
50 changes: 50 additions & 0 deletions plugins/AutoTask/v1/dataStreams/financialHealth.json
Original file line number Diff line number Diff line change
@@ -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 }] },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currency shape better?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can specify the currency dynamically if it's returned in the data / as a column FYI.

{ "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": ".*" }
]
}
46 changes: 46 additions & 0 deletions plugins/AutoTask/v1/dataStreams/projectStatus.json
Original file line number Diff line number Diff line change
@@ -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": ".*" }
]
}
21 changes: 21 additions & 0 deletions plugins/AutoTask/v1/dataStreams/projects.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
33 changes: 33 additions & 0 deletions plugins/AutoTask/v1/dataStreams/resourceUtilisation.json
Original file line number Diff line number Diff line change
@@ -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": ".*" }
]
}
31 changes: 31 additions & 0 deletions plugins/AutoTask/v1/dataStreams/resources.json
Original file line number Diff line number Diff line change
@@ -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": ".*" }
]
}
2 changes: 2 additions & 0 deletions plugins/AutoTask/v1/dataStreams/scripts/tickets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const selectedIds = new Set(context.objects.map(o => String(o.companyId)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume there's not an API parameter to filter by company ID/name?

result = (data.items ?? []).filter(ticket => selectedIds.has(String(ticket.companyID)));
Loading
Loading