diff --git a/lms/djangoapps/instructor/docs/decisions/0001-course-info-api-spec.rst b/lms/djangoapps/instructor/docs/decisions/0001-course-info-api-spec.rst new file mode 100644 index 000000000000..5ed90d9601e2 --- /dev/null +++ b/lms/djangoapps/instructor/docs/decisions/0001-course-info-api-spec.rst @@ -0,0 +1,66 @@ +Instructor Course Information API Specification +----------------------------------------------- + +Status +====== + +**Accepted** *2025-10-30* + +Context +======= + +The instructor dashboard requires comprehensive course metadata, enrollment statistics, user +permissions, and navigation configuration. This information was previously scattered across +multiple endpoints, requiring multiple round-trip requests and complex client-side data +aggregation for MFEs. + +Decisions +========= + +#. **Consolidated Course Metadata Endpoint** + + Create ``GET /api/instructor/v2/courses/{course_id}`` that returns comprehensive course + information in a single request, including course identity, timing, enrollment statistics, + user permissions, dashboard tab configuration, and operational information. + +#. **Permission-Based Tab Configuration** + + Server-side logic determines which dashboard tabs the current user can access based on + their roles, course features, and system configuration. Tabs are returned with URLs + pointing to the appropriate MFE routes. + +#. **Serializer-Based Business Logic** + + Use Django REST Framework serializers (``CourseInformationSerializer``) to encapsulate + all business logic for data gathering, permission checks, enrollment queries, and + formatting. Keep views thin. + +#. **OpenAPI Specification** + + Maintain an OpenAPI specification at ``../references/instructor-v2-course-info-spec.yaml`` to guide implementation. This static specification serves as a reference during development, but ``/api-docs/`` is the source of truth for what is actually deployed. Once implementation is complete and the endpoints are live in ``/api-docs/``, the static spec file will be deleted to avoid maintaining outdated documentation. + +Consequences +============ + +Positive +~~~~~~~~ + +* Single request replaces multiple round-trips, reducing latency for MFE page loads +* Centralized business logic ensures consistent permission checks and data formatting +* Simplified client code with all course information available in one call +* OpenAPI specification enables type-safe client generation + +Negative +~~~~~~~~ + +* Larger response payload (though offset by eliminating multiple requests) +* Some over-fetching when clients don't need all information +* Permission-based data prevents simple course-level caching +* Enrollment queries and permission checks run on every request + +References +========== + +* OpenAPI Specification: ``../references/instructor-v2-course-info-spec.yaml`` +* Implementation: ``lms/djangoapps/instructor/views/api_v2.py`` +* Live API Documentation: ``/api-docs/`` diff --git a/lms/djangoapps/instructor/docs/decisions/0002-instructor-grading-api-spec.rst b/lms/djangoapps/instructor/docs/decisions/0002-instructor-grading-api-spec.rst new file mode 100644 index 000000000000..c65464dab586 --- /dev/null +++ b/lms/djangoapps/instructor/docs/decisions/0002-instructor-grading-api-spec.rst @@ -0,0 +1,77 @@ +Instructor Grading API Specification +------------------------------------- + +Status +====== + +**Draft** (=> **Provisional**) + +Context +======= + +The instructor dashboard is being migrated to a Micro-Frontend (MFE) architecture, which requires RESTful API endpoints. The current implementation provides grading operations (reset attempts, rescore, override scores, delete state) through legacy endpoints. + +The MFE migration requires a modern, RESTful API with consistent URL patterns, clear synchronous vs asynchronous behavior, comprehensive task monitoring, and proper documentation. These operations need to support both single-learner (synchronous) and all-learners (asynchronous) execution models. + +Decisions +========= + +#. **RESTful Resource-Oriented Design** + + Use resource-oriented URLs: ``/api/instructor/v2/courses/{course_key}/{problem}/grading/{resource}`` + + Use appropriate HTTP methods: + + * ``GET`` for read operations (learner info, problem metadata, task status) + * ``POST`` for actions (reset attempts, rescore) + * ``PUT`` for replacements (score overrides) + * ``DELETE`` for removals (delete learner state) + +#. **Synchronous vs Asynchronous Execution** + + * Operations targeting a single learner (with ``learner`` parameter) execute synchronously + and return ``200 OK`` with immediate results (< 5s typical) + * Operations targeting all learners (no ``learner`` parameter) queue a background task + and return ``202 Accepted`` with task tracking information + * Provide task status endpoint: ``GET /api/instructor/v2/courses/{course_key}/tasks/{task_id}`` + +#. **Clear Operation Semantics** + + * **Reset Attempts**: Resets counter to zero, preserves answers/state + * **Delete State**: Permanently removes all learner data (requires ``learner`` parameter) + * **Rescore**: Re-evaluates submissions with current grading logic (supports ``only_if_higher``) + * **Override Score**: Manually sets specific score (requires ``learner`` parameter) + +#. **Consistent Response Formats** + + * Synchronous operations return ``SyncOperationResult`` with success, learner, problem_location, message + * Asynchronous operations return ``AsyncOperationResult`` with task_id, status_url, scope + * Task status responses include task_id, state, progress, result/error, timestamps + +#. **OpenAPI Specification** + + Maintain an OpenAPI specification at ``../references/instructor-v2-grading-api-spec.yaml`` to guide implementation. This static specification serves as a reference during development, but ``/api-docs/`` is the source of truth for what is actually deployed. Once implementation is complete and the endpoints are live in ``/api-docs/``, the static spec file will be deleted to avoid maintaining outdated documentation. + +Consequences +============ + +Positive +~~~~~~~~ + +* Consistent URL patterns and response formats make the API predictable +* Explicit sync/async behavior allows proper UI feedback +* OpenAPI specification enables automated validation, testing, and type-safe client generation +* Resource-oriented design makes it easy to add new operations + +Negative +~~~~~~~~ + +* Existing clients using legacy endpoints need to be updated +* Dual maintenance during transition period +* Developers familiar with legacy endpoints need to learn new patterns + +References +========== + +* OpenAPI Specification: ``../references/instructor-v2-grading-api-spec.yaml`` +* Live API Documentation: ``/api-docs/`` diff --git a/lms/djangoapps/instructor/docs/references/instructor-v2-course-info-spec.yaml b/lms/djangoapps/instructor/docs/references/instructor-v2-course-info-spec.yaml new file mode 100644 index 000000000000..28298440d155 --- /dev/null +++ b/lms/djangoapps/instructor/docs/references/instructor-v2-course-info-spec.yaml @@ -0,0 +1,484 @@ +swagger: '2.0' +info: + title: Instructor Dashboard API v2 + version: 2.0.0 + description: | + REST API for instructor dashboard operations. + + **Design Principles:** + - RESTful resource-oriented URLs + - Query parameters for filtering operations + - Clear separation between read and write operations + - Consistent error handling + +host: courses.example.com +basePath: / +schemes: + - https + +securityDefinitions: + JWTAuth: + type: apiKey + in: header + name: Authorization + description: JWT token authentication. Header format depends on JWT_AUTH['JWT_AUTH_HEADER_PREFIX'] setting (default is 'JWT '). + +security: + - JWTAuth: [] + +tags: + - name: Course + description: Course metadata and configuration + - name: Tasks + description: Background task monitoring + +paths: + /api/instructor/v2/courses/{course_id}: + get: + tags: + - Course + summary: Get course metadata + description: | + Retrieve comprehensive course metadata including enrollment counts, dashboard configuration, + permissions, and navigation sections. + operationId: getCourseMetadata + produces: + - application/json + parameters: + - $ref: '#/parameters/CourseId' + responses: + 200: + description: Course metadata retrieved successfully + schema: + $ref: '#/definitions/CourseInformation' + 400: + $ref: '#/responses/BadRequest' + 401: + $ref: '#/responses/Unauthorized' + 403: + $ref: '#/responses/Forbidden' + 404: + $ref: '#/responses/NotFound' + + /api/instructor/v2/courses/{course_id}/instructor_tasks: + get: + tags: + - Tasks + summary: List instructor tasks + description: | + List instructor tasks for a course with optional filtering by problem location and student. + + **Task States:** + - `PENDING`: Task is queued but not yet started + - `PROGRESS`: Task is currently executing + - `SUCCESS`: Task finished successfully + - `FAILURE`: Task encountered an error + - `REVOKED`: Task was cancelled + operationId: listInstructorTasks + produces: + - application/json + parameters: + - $ref: '#/parameters/CourseId' + - name: problem_location_str + in: query + description: Filter tasks to a specific problem location (usage key) + required: false + type: string + x-example: "block-v1:edX+DemoX+Demo_Course+type@problem+block@sample_problem" + - name: unique_student_identifier + in: query + description: Filter tasks to specific student (requires problem_location_str). Can be username or email. + required: false + type: string + x-example: "john_harvard" + responses: + 200: + description: Task list retrieved successfully + schema: + $ref: '#/definitions/InstructorTaskList' + examples: + application/json: + tasks: + - task_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + task_type: "rescore_problem" + task_state: "PROGRESS" + status: "In progress" + created: "2024-01-15T10:30:00Z" + task_input: '{"problem_url": "block-v1:edX+DemoX+Demo_Course+type@problem+block@hw1"}' + task_output: null + duration_sec: "45" + task_message: "Processing 75 of 150 students" + requester: "staff" + - task_id: "b2c3d4e5-f6a7-8901-bcde-f1234567890a" + task_type: "reset_problem_attempts" + task_state: "SUCCESS" + status: "Complete" + created: "2024-01-15T10:00:00Z" + task_input: '{"problem_url": "block-v1:edX+DemoX+Demo_Course+type@problem+block@hw2"}' + task_output: '{"total": 150, "succeeded": 150, "failed": 0}' + duration_sec: "120" + task_message: "Reset attempts for 150 students" + requester: "instructor" + 400: + $ref: '#/responses/BadRequest' + 401: + $ref: '#/responses/Unauthorized' + 403: + $ref: '#/responses/Forbidden' + 404: + $ref: '#/responses/NotFound' + +parameters: + CourseId: + name: course_id + in: path + required: true + description: Course identifier in format `course-v1:{org}+{course}+{run}` + type: string + pattern: '^course-v1:[^/+]+(\\+[^/+]+)+(\\+[^/]+)$' + x-example: "course-v1:edX+DemoX+Demo_Course" + +responses: + BadRequest: + description: Bad request - Invalid parameters or malformed request + schema: + $ref: '#/definitions/Error' + examples: + application/json: + error: "INVALID_PARAMETER" + message: "Invalid course key format" + + Unauthorized: + description: Unauthorized - Authentication required + schema: + $ref: '#/definitions/Error' + examples: + application/json: + error: "AUTHENTICATION_REQUIRED" + message: "You must be authenticated to access this endpoint" + + Forbidden: + description: Forbidden - Insufficient permissions + schema: + $ref: '#/definitions/Error' + examples: + application/json: + error: "PERMISSION_DENIED" + message: "You do not have instructor permissions for this course" + + NotFound: + description: Not found - Resource does not exist + schema: + $ref: '#/definitions/Error' + examples: + application/json: + error: "RESOURCE_NOT_FOUND" + message: "The specified resource does not exist" + +definitions: + CourseInformation: + type: object + description: Comprehensive course information including metadata, enrollment statistics, permissions, and dashboard configuration + required: + - course_id + - display_name + - org + - course_number + - pacing + - has_started + - has_ended + - total_enrollment + - enrollment_counts + - num_sections + - permissions + - tabs + - disable_buttons + properties: + course_id: + type: string + description: Course run key + example: "course-v1:edX+DemoX+Demo_Course" + display_name: + type: string + description: Course display name + example: "Demonstration Course" + org: + type: string + description: Organization identifier + example: "edX" + course_number: + type: string + description: Course number + example: "DemoX" + enrollment_start: + type: string + format: date-time + description: Enrollment start date (ISO 8601 with timezone) + example: "2013-02-05T00:00:00Z" + x-nullable: true + enrollment_end: + type: string + format: date-time + description: Enrollment end date (ISO 8601 with timezone) + example: "2024-12-31T23:59:59Z" + x-nullable: true + start: + type: string + format: date-time + description: Course start date (ISO 8601 with timezone) + example: "2013-02-05T05:00:00Z" + x-nullable: true + end: + type: string + format: date-time + description: Course end date (ISO 8601 with timezone) + example: "2024-12-31T23:59:59Z" + x-nullable: true + pacing: + type: string + enum: + - self + - instructor + description: Course pacing type + example: "instructor" + has_started: + type: boolean + description: Whether the course has started based on current time + example: true + has_ended: + type: boolean + description: Whether the course has ended based on current time + example: false + total_enrollment: + type: integer + minimum: 0 + description: Total number of enrollments across all modes + example: 150 + enrollment_counts: + type: object + description: Enrollment count breakdown by mode + properties: + total: + type: integer + minimum: 0 + audit: + type: integer + minimum: 0 + verified: + type: integer + minimum: 0 + honor: + type: integer + minimum: 0 + example: + total: 150 + audit: 100 + verified: 40 + honor: 10 + num_sections: + type: integer + minimum: 0 + description: Number of sections/chapters in the course + example: 12 + grade_cutoffs: + type: string + description: Formatted string of grade cutoffs + example: "A is 0.9, B is 0.8, C is 0.7, D is 0.6" + course_errors: + type: array + description: List of course validation errors from modulestore + items: + type: array + items: + type: string + example: [] + studio_url: + type: string + format: uri + description: URL to view/edit course in Studio + example: "https://studio.example.com/course/course-v1:edX+DemoX+Demo_Course" + permissions: + type: object + description: User permissions for instructor dashboard features + required: + - admin + - instructor + - finance_admin + - sales_admin + - staff + - forum_admin + - data_researcher + properties: + admin: + type: boolean + description: User is a platform administrator + instructor: + type: boolean + description: User has instructor role + finance_admin: + type: boolean + description: User has finance admin role + sales_admin: + type: boolean + description: User has sales admin role + staff: + type: boolean + description: User has staff role + forum_admin: + type: boolean + description: User has forum administrator role + data_researcher: + type: boolean + description: User has data researcher permissions + example: + admin: false + instructor: true + finance_admin: false + sales_admin: false + staff: true + forum_admin: true + data_researcher: false + tabs: + type: array + description: List of course tabs with configuration and display information + items: + type: object + required: + - tab_id + - title + - url + properties: + tab_id: + type: string + description: Unique tab identifier + title: + type: string + description: Display title for the tab + url: + type: string + format: uri + description: URL to access the tab + example: + - tab_id: "course_info" + title: "Course Info" + url: "https://mfe.example.com/instructor/course-v1:edX+DemoX+Demo_Course/course_info" + - tab_id: "enrollments" + title: "Enrollments" + url: "https://mfe.example.com/instructor/course-v1:edX+DemoX+Demo_Course/enrollments" + - tab_id: "grading" + title: "Grading" + url: "https://mfe.example.com/instructor/course-v1:edX+DemoX+Demo_Course/grading" + disable_buttons: + type: boolean + description: Whether to disable certain bulk action buttons due to large course size + example: false + analytics_dashboard_message: + type: string + description: Message about analytics dashboard availability + example: "To gain insights into student enrollment and participation, visit the analytics dashboard." + + InstructorTaskList: + type: object + description: List of instructor tasks + required: + - tasks + properties: + tasks: + type: array + items: + $ref: '#/definitions/InstructorTask' + + InstructorTask: + type: object + description: Instructor task details + required: + - task_id + - task_type + - task_state + - status + - created + - duration_sec + - task_message + - requester + - task_input + properties: + task_id: + type: string + description: Unique task identifier + example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + task_type: + type: string + description: Type of instructor task + enum: + - rescore_problem + - reset_problem_attempts + - delete_problem_state + - override_problem_score + - grade_course + - calculate_grades_csv + - problem_grade_report + - export_ora2_data + - cohort_students + - send_bulk_email + example: "rescore_problem" + task_state: + type: string + enum: + - PENDING + - PROGRESS + - SUCCESS + - FAILURE + - REVOKED + description: Current state of the task + example: "PROGRESS" + status: + type: string + description: Human-readable status message + example: "In progress" + created: + type: string + format: date-time + description: Task creation timestamp (ISO 8601 with timezone) + example: "2024-01-15T10:30:00Z" + duration_sec: + type: string + description: Task duration in seconds, or "unknown" if not available + example: "45" + task_message: + type: string + description: Detailed task message or progress update + example: "Processing 75 of 150 students" + requester: + type: string + description: Username of the user who initiated the task + example: "instructor" + task_input: + type: string + description: JSON string of task input parameters + example: '{"problem_url": "block-v1:edX+DemoX+Demo_Course+type@problem+block@hw1"}' + task_output: + type: string + description: JSON string of task output/results (null if not complete) + example: '{"total": 150, "succeeded": 150, "failed": 0}' + x-nullable: true + + Error: + type: object + description: Error response + required: + - error + - message + properties: + error: + type: string + description: Machine-readable error code + example: "RESOURCE_NOT_FOUND" + message: + type: string + description: Human-readable error message + example: "The specified course does not exist" + field_errors: + type: object + description: Field-specific validation errors (if applicable) + additionalProperties: + type: string diff --git a/lms/djangoapps/instructor/docs/references/instructor-v2-grading-api-spec.yaml b/lms/djangoapps/instructor/docs/references/instructor-v2-grading-api-spec.yaml new file mode 100644 index 000000000000..4fea37ebb5ec --- /dev/null +++ b/lms/djangoapps/instructor/docs/references/instructor-v2-grading-api-spec.yaml @@ -0,0 +1,654 @@ +swagger: '2.0' +info: + title: Instructor Dashboard API + version: 2.0.0 + description: | + Modern REST API for instructor dashboard operations. + + **Design Principles:** + - RESTful resource-oriented URLs + - Query parameters for filtering operations + - Clear separation between read and write operations + - Consistent error handling + + **Execution Model:** + - Operations that affect a single learner execute synchronously (< 5s typical) + - Operations that affect all learners queue a background task + - Use the task status endpoint to monitor background tasks + +host: courses.example.com +basePath: / +schemes: + - https + +securityDefinitions: + JWTAuth: + type: apiKey + in: header + name: Authorization + description: JWT token authentication. Header format depends on JWT_AUTH['JWT_AUTH_HEADER_PREFIX'] setting (default is 'JWT '). + +security: + - JWTAuth: [] + +tags: + - name: Learners + description: Learner information and enrollment data + - name: Problems + description: Problem metadata and structure + - name: Grading + description: Grading operations and score management + - name: Tasks + description: Background task monitoring + +paths: + # ==================== LEARNER ENDPOINTS ==================== + + /api/instructor/v2/courses/{course_key}/learners/{email_or_username}: + get: + tags: + - Learners + summary: Get learner information + description: | + Retrieve comprehensive learner information including profile, enrollment status, + progress URLs, and current grading data. + operationId: getLearner + produces: + - application/json + parameters: + - $ref: '#/parameters/CourseKey' + - $ref: '#/parameters/LearnerIdentifierPath' + responses: + 200: + description: Learner information retrieved successfully + schema: + $ref: '#/definitions/Learner' + 400: + $ref: '#/responses/BadRequest' + 401: + $ref: '#/responses/Unauthorized' + 403: + $ref: '#/responses/Forbidden' + 404: + $ref: '#/responses/NotFound' + + # ==================== PROBLEM ENDPOINTS ==================== + + /api/instructor/v2/courses/{course_key}/problems/{location}: + get: + tags: + - Problems + summary: Get problem information + description: | + Retrieve problem metadata including display name, location in course hierarchy, + and usage key. + + **Note:** Requires exact problem location - no search or partial matching. + operationId: getProblem + produces: + - application/json + parameters: + - $ref: '#/parameters/CourseKey' + - name: location + in: path + description: Problem block usage key + required: true + type: string + x-example: "block-v1:edX+DemoX+Demo_Course+type@problem+block@sample_problem" + responses: + 200: + description: Problem information retrieved successfully + schema: + $ref: '#/definitions/Problem' + 400: + $ref: '#/responses/BadRequest' + 401: + $ref: '#/responses/Unauthorized' + 403: + $ref: '#/responses/Forbidden' + 404: + $ref: '#/responses/NotFound' + + # ==================== GRADING ENDPOINTS ==================== + + /api/instructor/v2/courses/{course_key}/{problem}/grading/attempts/reset: + post: + tags: + - Grading + summary: Reset problem attempts + description: | + Reset attempt counters to zero, allowing learner(s) to reattempt the problem. + The learner's previous answers and state are preserved, only the attempt counter is reset. + + **Scope:** + - With `learner`: Single learner, single problem (synchronous, ~100-500ms) + - Without `learner`: All learners, single problem (async task) + + **Note:** To completely delete state instead of just resetting attempts, + use `DELETE /grading/state` instead. + operationId: resetAttempts + produces: + - application/json + parameters: + - $ref: '#/parameters/CourseKey' + - $ref: '#/parameters/ProblemLocationPath' + - $ref: '#/parameters/LearnerIdentifierQuery' + responses: + 200: + description: Operation completed successfully (synchronous) + schema: + $ref: '#/definitions/SyncOperationResult' + 202: + description: Task queued for background processing (asynchronous) + schema: + $ref: '#/definitions/AsyncOperationResult' + 400: + $ref: '#/responses/BadRequest' + 401: + $ref: '#/responses/Unauthorized' + 403: + $ref: '#/responses/Forbidden' + 404: + $ref: '#/responses/NotFound' + + /api/instructor/v2/courses/{course_key}/{problem}/grading/state: + delete: + tags: + - Grading + summary: Delete learner problem state + description: | + Permanently delete a specific learner's StudentModule record for a specific problem, + including all answers, submissions, attempts, and scores from the database. + + **Warning:** This operation is destructive and cannot be undone. Unlike resetting + attempts (which preserves state), this completely removes all records. + + **Requirements:** + - `learner` parameter is required + - Always executes synchronously + operationId: deleteState + produces: + - application/json + parameters: + - $ref: '#/parameters/CourseKey' + - $ref: '#/parameters/ProblemLocationPath' + - name: learner + in: query + description: Learner username or email (required) + required: true + type: string + x-example: "john_harvard" + responses: + 200: + description: State deleted successfully + schema: + $ref: '#/definitions/SyncOperationResult' + 400: + $ref: '#/responses/BadRequest' + 401: + $ref: '#/responses/Unauthorized' + 403: + $ref: '#/responses/Forbidden' + 404: + $ref: '#/responses/NotFound' + + /api/instructor/v2/courses/{course_key}/{problem}/grading/scores/rescore: + post: + tags: + - Grading + summary: Rescore problem submissions + description: | + Re-evaluate learner submissions and update scores based on current grading logic. + + **Scope:** + - With `learner`: Single learner, single problem (synchronous) + - Without `learner`: All learners, single problem (async task) + operationId: rescore + produces: + - application/json + parameters: + - $ref: '#/parameters/CourseKey' + - $ref: '#/parameters/ProblemLocationPath' + - $ref: '#/parameters/LearnerIdentifierQuery' + - name: only_if_higher + in: query + description: Only update score if the new score is higher than current score + required: false + type: boolean + default: false + responses: + 200: + description: Rescore completed successfully (synchronous) + schema: + $ref: '#/definitions/SyncOperationResult' + 202: + description: Rescore task queued for background processing (asynchronous) + schema: + $ref: '#/definitions/AsyncOperationResult' + 400: + $ref: '#/responses/BadRequest' + 401: + $ref: '#/responses/Unauthorized' + 403: + $ref: '#/responses/Forbidden' + 404: + $ref: '#/responses/NotFound' + + /api/instructor/v2/courses/{course_key}/{problem}/grading/scores: + put: + tags: + - Grading + summary: Override a learner's score + description: | + Manually set a specific score for a learner on a problem, replacing any + automatically calculated score. + + **Requirements:** + - `learner` parameter is required + - Always executes synchronously + + **Note:** This creates or updates a PersistentSubsectionGradeOverride record. + operationId: overrideScore + consumes: + - application/json + produces: + - application/json + parameters: + - $ref: '#/parameters/CourseKey' + - $ref: '#/parameters/ProblemLocationPath' + - name: learner + in: query + description: Learner username or email (required) + required: true + type: string + x-example: "john_harvard" + - name: body + in: body + required: true + schema: + type: object + required: + - score + properties: + score: + type: number + description: New score value (out of problem's total possible points) + minimum: 0 + example: 8.5 + responses: + 200: + description: Score overridden successfully + schema: + $ref: '#/definitions/SyncOperationResult' + examples: + application/json: + success: true + learner: "john_harvard" + problem_location: "block-v1:edX+DemoX+Demo_Course+type@problem+block@sample_problem" + score: 8.5 + previous_score: 5.0 + message: "Score overridden successfully" + 400: + $ref: '#/responses/BadRequest' + 401: + $ref: '#/responses/Unauthorized' + 403: + $ref: '#/responses/Forbidden' + 404: + $ref: '#/responses/NotFound' + + # ==================== TASK ENDPOINTS ==================== + + /api/instructor/v2/courses/{course_key}/tasks/{task_id}: + get: + tags: + - Tasks + summary: Get task status + description: | + Check the status of a background task. + + **Task States:** + - `pending`: Task is queued but not yet started + - `running`: Task is currently executing + - `completed`: Task finished successfully + - `failed`: Task encountered an error + operationId: getTaskStatus + produces: + - application/json + parameters: + - $ref: '#/parameters/CourseKey' + - name: task_id + in: path + description: Task identifier returned from async operation + required: true + type: string + responses: + 200: + description: Task status retrieved successfully + schema: + $ref: '#/definitions/TaskStatus' + examples: + application/json: + task_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + state: "completed" + progress: + current: 150 + total: 150 + result: + success: true + message: "Reset attempts for 150 learners" + created_at: "2024-01-15T10:30:00Z" + updated_at: "2024-01-15T10:35:23Z" + 400: + $ref: '#/responses/BadRequest' + 401: + $ref: '#/responses/Unauthorized' + 403: + $ref: '#/responses/Forbidden' + 404: + $ref: '#/responses/NotFound' + +# ==================== COMPONENTS ==================== + +parameters: + CourseKey: + name: course_key + in: path + required: true + description: Course identifier in format `course-v1:{org}+{course}+{run}` + type: string + pattern: '^course-v1:[^/+]+(\\+[^/+]+)+(\\+[^/]+)$' + x-example: "course-v1:edX+DemoX+Demo_Course" + + LearnerIdentifierPath: + name: email_or_username + in: path + required: true + description: Learner's username or email address + type: string + minLength: 1 + + LearnerIdentifierQuery: + name: learner + in: query + required: false + description: | + Learner's username or email address. + If omitted, operation applies to all learners in the course. + type: string + x-example: "john_harvard" + + ProblemLocationPath: + name: problem + in: path + required: true + description: Problem block usage key + type: string + x-example: "block-v1:edX+DemoX+Demo_Course+type@problem+block@sample_problem" + +responses: + BadRequest: + description: Bad request - Invalid parameters or malformed request + schema: + $ref: '#/definitions/Error' + examples: + application/json: + error: "INVALID_PARAMETER" + message: "Invalid course key format" + status_code: 400 + + Unauthorized: + description: Unauthorized - Authentication required + schema: + $ref: '#/definitions/Error' + examples: + application/json: + error: "AUTHENTICATION_REQUIRED" + message: "You must be authenticated to access this endpoint" + status_code: 401 + + Forbidden: + description: Forbidden - Insufficient permissions + schema: + $ref: '#/definitions/Error' + examples: + application/json: + error: "PERMISSION_DENIED" + message: "You do not have instructor permissions for this course" + status_code: 403 + + NotFound: + description: Not found - Resource does not exist + schema: + $ref: '#/definitions/Error' + examples: + application/json: + error: "RESOURCE_NOT_FOUND" + message: "The specified resource does not exist" + status_code: 404 + +definitions: + SyncOperationResult: + type: object + description: Result from a synchronous grading operation + required: + - success + properties: + success: + type: boolean + description: Whether the operation succeeded + learner: + type: string + description: Learner identifier (if applicable) + problem_location: + type: string + description: Problem location (if applicable) + score: + type: number + description: Updated score (for override operations) + message: + type: string + description: Human-readable result message + example: + success: true + learner: "john_harvard" + problem_location: "block-v1:edX+DemoX+Demo_Course+type@problem+block@hw1_p1" + message: "Operation completed successfully" + + AsyncOperationResult: + type: object + description: Task information for an asynchronous operation + required: + - task_id + - status_url + properties: + task_id: + type: string + description: Unique task identifier + status_url: + type: string + format: uri + description: URL to poll for task status + scope: + type: object + description: Scope of the operation + properties: + learners: + type: string + description: Either "all" or specific learner identifier + problems: + type: string + description: Either "all" or specific problem location + example: + task_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + status_url: "/api/instructor/v2/courses/course-v1:edX+DemoX+Demo_Course/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890" + scope: + learners: "all" + problem_location: "block-v1:edX+DemoX+Demo_Course+type@problem+block@hw1_p1" + + TaskStatus: + type: object + description: Status of a background task + required: + - task_id + - state + - created_at + - updated_at + properties: + task_id: + type: string + description: Task identifier + state: + type: string + enum: + - pending + - running + - completed + - failed + description: Current state of the task + progress: + type: object + description: Progress information (if available) + properties: + current: + type: integer + minimum: 0 + total: + type: integer + minimum: 0 + required: + - current + - total + result: + type: object + description: Task result (present when state is "completed") + properties: + success: + type: boolean + message: + type: string + error: + type: object + description: Error information (present when state is "failed") + properties: + code: + type: string + message: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + + Learner: + type: object + description: Comprehensive learner information + required: + - username + - email + - first_name + - last_name + properties: + username: + type: string + example: "john_harvard" + email: + type: string + format: email + example: "john@example.com" + first_name: + type: string + example: "John" + last_name: + type: string + example: "Harvard" + progress_url: + type: string + format: uri + description: URL to learner's progress page + x-nullable: true + gradebook_url: + type: string + format: uri + description: URL to learner's gradebook view + x-nullable: true + current_score: + type: object + x-nullable: true + properties: + score: + type: number + format: float + minimum: 0 + total: + type: number + format: float + minimum: 0 + attempts: + type: object + x-nullable: true + properties: + current: + type: integer + minimum: 0 + total: + type: integer + minimum: 0 + + Problem: + type: object + description: Problem metadata and location + required: + - id + - name + - breadcrumbs + properties: + id: + type: string + description: Problem usage key + example: "block-v1:edX+DemoX+Demo_Course+type@problem+block@sample_problem" + name: + type: string + description: Problem display name + example: "Sample Problem" + breadcrumbs: + type: array + description: Course hierarchy breadcrumbs + items: + type: object + required: + - display_name + properties: + display_name: + type: string + usage_key: + type: string + description: Block usage key (omitted for course level) + + Error: + type: object + description: Error response + required: + - error + - message + - status_code + properties: + error: + type: string + description: Machine-readable error code + example: "RESOURCE_NOT_FOUND" + message: + type: string + description: Human-readable error message + example: "The specified course does not exist" + status_code: + type: integer + description: HTTP status code + example: 404 + field_errors: + type: object + description: Field-specific validation errors (if applicable) + additionalProperties: + type: string