diff --git a/components/d2l_brightspace/actions/create-enrollment/create-enrollment.mjs b/components/d2l_brightspace/actions/create-enrollment/create-enrollment.mjs new file mode 100644 index 0000000000000..d2fdf946fb1d0 --- /dev/null +++ b/components/d2l_brightspace/actions/create-enrollment/create-enrollment.mjs @@ -0,0 +1,49 @@ +import app from "../../d2l_brightspace.app.mjs"; + +export default { + key: "d2l_brightspace-create-enrollment", + name: "Create Enrollment", + description: "Enrolls a user in a course on D2L Brightspace. [See the documentation](https://docs.valence.desire2learn.com/res/enroll.html)", + version: "0.0.1", + type: "action", + props: { + app, + orgUnitId: { + propDefinition: [ + app, + "orgUnitId", + ], + }, + userId: { + propDefinition: [ + app, + "userId", + ], + }, + roleId: { + propDefinition: [ + app, + "roleId", + ], + }, + }, + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + idempotentHint: false, + }, + async run({ $ }) { + const response = await this.app.createEnrollment({ + data: { + OrgUnitId: this.orgUnitId, + UserId: this.userId, + RoleId: this.roleId, + }, + $, + }); + + $.export("$summary", `Successfully enrolled user ${this.userId} in course ${this.orgUnitId}`); + return response; + }, +}; diff --git a/components/d2l_brightspace/actions/get-grade-values/get-grade-values.mjs b/components/d2l_brightspace/actions/get-grade-values/get-grade-values.mjs new file mode 100644 index 0000000000000..3fa0fc271a019 --- /dev/null +++ b/components/d2l_brightspace/actions/get-grade-values/get-grade-values.mjs @@ -0,0 +1,50 @@ +import app from "../../d2l_brightspace.app.mjs"; + +export default { + key: "d2l_brightspace-get-grade-values", + name: "Get Grade Values", + description: "Retrieves the grade value for a specific user and grade object from D2L Brightspace. [See the documentation](https://docs.valence.desire2learn.com/res/grade.html)", + version: "0.0.1", + type: "action", + props: { + app, + orgUnitId: { + propDefinition: [ + app, + "orgUnitId", + ], + }, + gradeObjectId: { + propDefinition: [ + app, + "gradeObjectId", + (c) => ({ + orgUnitId: c.orgUnitId, + }), + ], + }, + userId: { + propDefinition: [ + app, + "userId", + ], + }, + }, + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + idempotentHint: true, + }, + async run({ $ }) { + const response = await this.app.getGradeValue({ + orgUnitId: this.orgUnitId, + gradeObjectId: this.gradeObjectId, + userId: this.userId, + $, + }); + + $.export("$summary", `Successfully retrieved grade value for user ${this.userId}`); + return response; + }, +}; diff --git a/components/d2l_brightspace/actions/get-user/get-user.mjs b/components/d2l_brightspace/actions/get-user/get-user.mjs new file mode 100644 index 0000000000000..7f2ee67a91feb --- /dev/null +++ b/components/d2l_brightspace/actions/get-user/get-user.mjs @@ -0,0 +1,33 @@ +import app from "../../d2l_brightspace.app.mjs"; + +export default { + key: "d2l_brightspace-get-user", + name: "Get User", + description: "Retrieves information about a specific user from D2L Brightspace. [See the documentation](https://docs.valence.desire2learn.com/res/user.html)", + version: "0.0.1", + type: "action", + props: { + app, + userId: { + propDefinition: [ + app, + "userId", + ], + }, + }, + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + idempotentHint: true, + }, + async run({ $ }) { + const response = await this.app.getUser({ + userId: this.userId, + $, + }); + + $.export("$summary", `Successfully retrieved user ${response.FirstName} ${response.LastName}`); + return response; + }, +}; diff --git a/components/d2l_brightspace/actions/list-dropbox-folders/list-dropbox-folders.mjs b/components/d2l_brightspace/actions/list-dropbox-folders/list-dropbox-folders.mjs new file mode 100644 index 0000000000000..73268d5525761 --- /dev/null +++ b/components/d2l_brightspace/actions/list-dropbox-folders/list-dropbox-folders.mjs @@ -0,0 +1,38 @@ +import app from "../../d2l_brightspace.app.mjs"; + +export default { + key: "d2l_brightspace-list-dropbox-folders", + name: "List Dropbox Folders", + description: "Retrieves all dropbox folders (assignments) for a specific course from D2L Brightspace. [See the documentation](https://docs.valence.desire2learn.com/res/dropbox.html)", + version: "0.0.1", + type: "action", + props: { + app, + orgUnitId: { + propDefinition: [ + app, + "orgUnitId", + ], + }, + }, + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + idempotentHint: true, + }, + async run({ $ }) { + const response = await this.app.listDropboxFolders({ + orgUnitId: this.orgUnitId, + $, + }); + + const folders = Array.isArray(response) + ? response + : []; + $.export("$summary", `Successfully retrieved ${folders.length} dropbox folder${folders.length === 1 + ? "" + : "s"}`); + return folders; + }, +}; diff --git a/components/d2l_brightspace/actions/list-enrollments/list-enrollments.mjs b/components/d2l_brightspace/actions/list-enrollments/list-enrollments.mjs new file mode 100644 index 0000000000000..97c7797a22d05 --- /dev/null +++ b/components/d2l_brightspace/actions/list-enrollments/list-enrollments.mjs @@ -0,0 +1,65 @@ +import app from "../../d2l_brightspace.app.mjs"; + +export default { + key: "d2l_brightspace-list-enrollments", + name: "List Enrollments", + description: "Retrieves a list of enrollments for a specific course from D2L Brightspace. [See the documentation](https://docs.valence.desire2learn.com/res/enroll.html)", + version: "0.0.1", + type: "action", + props: { + app, + orgUnitId: { + propDefinition: [ + app, + "orgUnitId", + ], + }, + roleId: { + propDefinition: [ + app, + "roleId", + ], + optional: true, + description: "Filter enrollments by role (optional)", + }, + max: { + type: "integer", + label: "Maximum Results", + description: "The maximum number of enrollments to return", + optional: true, + default: 100, + }, + }, + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + idempotentHint: true, + }, + async run({ $ }) { + const enrollments = []; + + const params = {}; + if (this.roleId) { + params.roleId = this.roleId; + } + + const iterator = this.app.paginate({ + fn: this.app.listEnrollmentsByOrgUnit, + params: { + orgUnitId: this.orgUnitId, + ...params, + }, + maxResults: this.max, + }); + + for await (const enrollment of iterator) { + enrollments.push(enrollment); + } + + $.export("$summary", `Successfully retrieved ${enrollments.length} enrollment${enrollments.length === 1 + ? "" + : "s"}`); + return enrollments; + }, +}; diff --git a/components/d2l_brightspace/actions/list-org-units/list-org-units.mjs b/components/d2l_brightspace/actions/list-org-units/list-org-units.mjs new file mode 100644 index 0000000000000..7fce032bc01f3 --- /dev/null +++ b/components/d2l_brightspace/actions/list-org-units/list-org-units.mjs @@ -0,0 +1,72 @@ +import app from "../../d2l_brightspace.app.mjs"; + +export default { + key: "d2l_brightspace-list-org-units", + name: "List Organizational Units", + description: "Retrieves a list of organizational units (courses, departments, etc.) from D2L Brightspace. [See the documentation](https://docs.valence.desire2learn.com/res/orgunit.html)", + version: "0.0.1", + type: "action", + props: { + app, + orgUnitType: { + type: "string", + label: "Org Unit Type", + description: "Filter by organizational unit type ID (e.g., type ID for course offerings). Leave empty to retrieve all types.", + optional: true, + }, + orgUnitName: { + type: "string", + label: "Org Unit Name Filter", + description: "Filter by names containing this substring", + optional: true, + }, + orgUnitCode: { + type: "string", + label: "Org Unit Code Filter", + description: "Filter by codes containing this substring", + optional: true, + }, + max: { + type: "integer", + label: "Maximum Results", + description: "The maximum number of organizational units to return", + optional: true, + default: 100, + }, + }, + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + async run({ $ }) { + const { + app, + orgUnitType, + orgUnitName, + orgUnitCode, + max, + } = this; + + const orgUnits = []; + + const iterator = app.paginate({ + fn: app.listOrgUnits, + params: { + orgUnitType, + orgUnitName, + orgUnitCode, + }, + maxResults: max, + }); + + for await (const orgUnit of iterator) { + orgUnits.push(orgUnit); + } + + $.export("$summary", `Successfully retrieved ${orgUnits.length} organizational unit${orgUnits.length === 1 + ? "" + : "s"}`); + return orgUnits; + }, +}; diff --git a/components/d2l_brightspace/common/constants.mjs b/components/d2l_brightspace/common/constants.mjs new file mode 100644 index 0000000000000..914fc4bfde88b --- /dev/null +++ b/components/d2l_brightspace/common/constants.mjs @@ -0,0 +1,20 @@ +const DOMAIN_PLACEHOLDER = "{domain}"; +const BASE_URL_TEMPLATE = `https://${DOMAIN_PLACEHOLDER}/d2l/api`; +const DEFAULT_LIMIT = 100; +const DEFAULT_MAX = 600; +const LP_VERSION = "1.0"; +const LE_VERSION = "1.0"; +const API_CONTEXTS = { + LP: "lp", + LE: "le", +}; + +export default { + BASE_URL_TEMPLATE, + DOMAIN_PLACEHOLDER, + DEFAULT_LIMIT, + DEFAULT_MAX, + LP_VERSION, + LE_VERSION, + API_CONTEXTS, +}; diff --git a/components/d2l_brightspace/d2l_brightspace.app.mjs b/components/d2l_brightspace/d2l_brightspace.app.mjs index 0d2718091ed74..81ea096c30153 100644 --- a/components/d2l_brightspace/d2l_brightspace.app.mjs +++ b/components/d2l_brightspace/d2l_brightspace.app.mjs @@ -1,11 +1,216 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "d2l_brightspace", - propDefinitions: {}, + propDefinitions: { + orgUnitId: { + type: "string", + label: "Organizational Unit", + description: "The organizational unit ID (course, department, etc.)", + async options({ + prevContext, orgUnitType, + }) { + const params = { + limit: constants.DEFAULT_LIMIT, + }; + if (prevContext?.bookmark) { + params.bookmark = prevContext.bookmark; + } + if (orgUnitType) { + params.orgUnitType = orgUnitType; + } + const response = await this.listOrgUnits({ + params, + }); + const orgUnits = response.Items || response || []; + const options = orgUnits.map((orgUnit) => ({ + label: `${orgUnit.Name} (${orgUnit.Code || orgUnit.Type?.Name || ""})`, + value: orgUnit.Identifier, + })); + return { + options, + context: { + bookmark: response.PagingInfo?.Bookmark, + }, + }; + }, + }, + userId: { + type: "string", + label: "User", + description: "The user ID", + async options({ prevContext }) { + const params = { + limit: constants.DEFAULT_LIMIT, + }; + if (prevContext?.bookmark) { + params.bookmark = prevContext.bookmark; + } + const response = await this.listUsers({ + params, + }); + const users = response.Items || response || []; + const options = users.map((user) => ({ + label: `${user.FirstName} ${user.LastName} (${user.UserName})`, + value: user.UserId || user.Identifier, + })); + return { + options, + context: { + bookmark: response.PagingInfo?.Bookmark, + }, + }; + }, + }, + roleId: { + type: "string", + label: "Role", + description: "The enrollment role ID", + async options() { + const response = await this.listRoles(); + const roles = response.Items || response || []; + return roles.map((role) => ({ + label: role.DisplayName || role.Name, + value: role.RoleId || role.Identifier, + })); + }, + }, + gradeObjectId: { + type: "string", + label: "Grade Object", + description: "The grade object (assessment) ID", + async options({ orgUnitId }) { + if (!orgUnitId) { + return []; + } + const response = await this.listGrades({ + orgUnitId, + }); + const grades = response || []; + return grades.map((grade) => ({ + label: grade.Name, + value: grade.Id, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getUrl(path) { + return `${constants.BASE_URL_TEMPLATE.replace( + constants.DOMAIN_PLACEHOLDER, + this.$auth.target_host, + )}${path}`; + }, + _headers() { + return { + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + "Content-Type": "application/json", + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this.getUrl(path), + headers: this._headers(), + ...opts, + }); + }, + listOrgUnits(opts = {}) { + return this._makeRequest({ + path: `/${constants.API_CONTEXTS.LP}/${constants.LP_VERSION}/orgstructure/`, + ...opts, + }); + }, + listUsers(opts = {}) { + return this._makeRequest({ + path: `/${constants.API_CONTEXTS.LP}/${constants.LP_VERSION}/users/`, + ...opts, + }); + }, + getUser({ + userId, ...opts + }) { + return this._makeRequest({ + path: `/${constants.API_CONTEXTS.LP}/${constants.LP_VERSION}/users/${userId}`, + ...opts, + }); + }, + listRoles(opts = {}) { + return this._makeRequest({ + path: `/${constants.API_CONTEXTS.LP}/${constants.LP_VERSION}/roles/`, + ...opts, + }); + }, + createEnrollment(opts = {}) { + return this._makeRequest({ + method: "POST", + path: `/${constants.API_CONTEXTS.LP}/${constants.LP_VERSION}/enrollments/`, + ...opts, + }); + }, + listEnrollmentsByOrgUnit({ + orgUnitId, ...opts + }) { + return this._makeRequest({ + path: `/${constants.API_CONTEXTS.LP}/${constants.LP_VERSION}/enrollments/orgUnits/${orgUnitId}/users/`, + ...opts, + }); + }, + listGrades({ + orgUnitId, ...opts + }) { + return this._makeRequest({ + path: `/${constants.API_CONTEXTS.LE}/${constants.LE_VERSION}/${orgUnitId}/grades/`, + ...opts, + }); + }, + getGradeValue({ + orgUnitId, gradeObjectId, userId, ...opts + }) { + return this._makeRequest({ + path: `/${constants.API_CONTEXTS.LE}/${constants.LE_VERSION}/${orgUnitId}/grades/${gradeObjectId}/values/${userId}`, + ...opts, + }); + }, + listDropboxFolders({ + orgUnitId, ...opts + }) { + return this._makeRequest({ + path: `/${constants.API_CONTEXTS.LE}/${constants.LE_VERSION}/${orgUnitId}/dropbox/folders/`, + ...opts, + }); + }, + async *paginate({ + fn, params = {}, maxResults = null, + }) { + let count = 0; + let hasMore = true; + + while (hasMore) { + const response = await fn({ + params, + }); + const items = response.Items || response.Objects || response; + + if (Array.isArray(items)) { + for (const item of items) { + yield item; + if (maxResults && ++count === maxResults) { + return; + } + } + } + + if (response.PagingInfo) { + params.bookmark = response.PagingInfo.Bookmark; + hasMore = response.PagingInfo.HasMoreItems; + } else { + hasMore = false; + } + } }, }, }; diff --git a/components/d2l_brightspace/package.json b/components/d2l_brightspace/package.json index 01145376def66..0c7fff1aa0fee 100644 --- a/components/d2l_brightspace/package.json +++ b/components/d2l_brightspace/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/d2l_brightspace", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream D2L Brightspace Components", "main": "d2l_brightspace.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e8205f79102e5..419c46ccdea1d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3660,7 +3660,11 @@ importers: components/cyfe: {} - components/d2l_brightspace: {} + components/d2l_brightspace: + dependencies: + '@pipedream/platform': + specifier: ^3.1.1 + version: 3.1.1 components/d7_darwin: {} @@ -5412,8 +5416,7 @@ importers: components/flotiq: {} - components/flowii: - specifiers: {} + components/flowii: {} components/flowiseai: dependencies: @@ -6710,8 +6713,7 @@ importers: specifier: ^3.1.1 version: 3.1.1 - components/hailey_hr: - specifiers: {} + components/hailey_hr: {} components/halo_service_solutions: {} @@ -10147,8 +10149,7 @@ importers: specifier: ^1.6.8 version: 1.6.8 - components/nuvemshop: - specifiers: {} + components/nuvemshop: {} components/nvoip: {} @@ -14562,8 +14563,7 @@ importers: components/sugarcrm_: {} - components/suitecrm: - specifiers: {} + components/suitecrm: {} components/suitedash: dependencies: