From 33c6a87220432e8925e1f98bc8bf39f62a5d54d0 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Fri, 26 May 2023 10:30:15 +0200 Subject: [PATCH] Add support for component based documents (EpsilonComponents) (#89) * Initial component architecture for JSON and Word export * Added architecture for single point of component data retrieval and Word convertion * Typo * Cleanup component architecture and modify Word export to return OpenXML * Add coding guidelines enforcement (#85) * Add initial coding guidelines and fix where necessary * Enforce code styling and update code where necessary * Remove duplicate .editorconfig entries * Add code analysis setup to CI * Add missing checkout step * Update incorrect action version tag * Remove incompatible analysis step * Push code style offense to validate CI pipeline * Remove code style violation * Add global.json * Add support for dynamic component converters * Removed Word component merging logic from controller to WordDownloader class * Remve redundant method * Remove accidental document * Remove redundant models and interfaces * Fix persona images not being added as Base64 (#84) * Made some adjustments that the pictures in the persona get converted to Base64 strings versus loading it remotely from Canvas (as the access key is only valid for a limited time period) * Add coding guidelines enforcement (#85) * Add initial coding guidelines and fix where necessary * Enforce code styling and update code where necessary * Remove duplicate .editorconfig entries * Add code analysis setup to CI * Add missing checkout step * Update incorrect action version tag * Remove incompatible analysis step * Push code style offense to validate CI pipeline * Remove code style violation * Add global.json * Made a couple of changes based on recommendations made in the merge request. --------- Co-authored-by: Jelle Maas * Fix exporters no longer working correctly (#88) * Temporarily fix paginator not working correctly * Set format names to uppercase * Fix stream being disposed before usage * Fix homepage returning null when no images are present * Cleanup Word downloader * Change CLI project namespace to allow for additional projects * Add initial Web API project * Create controllers and endpoints * Transform abstract class to interface * Change classes to records & add poc endpoints * Add download Word file endpoint * Typo * Add initial frontend code * Add competence profile data model (#76) * Initial proof of concept for competence profile data model * Refactor competence profile return type * Created endpoint with updated HboIDomain and Mockdata. Also created GraphQL endpoint for student data * Cleanup records to seperate GraphQL folder * Fix GraphQL records --------- Co-authored-by: Jelle Maas Co-authored-by: Sven Hansen <76601644+1SvenHansen@users.noreply.github.com> * Link submission query with competence profile endpoint (#79) * Added Professional development * Rename namespace to correspond with coding guidelines * Separate records into different files * Rename Value to Name to correspond with domain * Rename Value to Level to correspond with domain * Add professional skills to domain * Add short name to architectural layer and professional skills * Rename properties in competence profile random data filler * Rename CompetenceProfileOutcome to ProfessionalTaskOutcome * Add Professional skill outcome to competence profile * Use singular name * Use id references instead of name values * Use type keyword * Add submissions from all courses from student to competence profile transformation * Fix merge issues * Add task and skill outcomes to competence profile return type * Fetch terms from canvas * Remove redundant imports * Change default application url * Prevent browser from launching on every restart * Cleanup code * Add filter enrolment term function * Add terms to competence profile * Remove unused terms function * Added filter on professional outcomes and sorting * Removed unused things * V0.1 * Defining colors for elements * Remove obsolete styling * Update pnpm lock file * Add Vue router * Resolve ESLint warnings * Add authorization view and controller * Use controller templating for route * A lot of stuff * A lot of stuff * Working * Set it all to vue 3 supported format * Fix import path * Add CORS policy * Realtime user data * Add terms to competence profile * Cleanup code and improve competence profile converter * Filter on PostedAt value --------- Co-authored-by: Tara Co-authored-by: Sven Co-authored-by: Neal Geilen * Add performance dashboard frontend interface (#81) * Added Professional development * Rename namespace to correspond with coding guidelines * Separate records into different files * Rename Value to Name to correspond with domain * Rename Value to Level to correspond with domain * Add professional skills to domain * Add short name to architectural layer and professional skills * Rename properties in competence profile random data filler * Rename CompetenceProfileOutcome to ProfessionalTaskOutcome * Add Professional skill outcome to competence profile * Use singular name * Use id references instead of name values * Use type keyword * Add submissions from all courses from student to competence profile transformation * Fix merge issues * Add task and skill outcomes to competence profile return type * Fetch terms from canvas * Remove redundant imports * Change default application url * Prevent browser from launching on every restart * Cleanup code * V0.1 * Defining colors for elements * Remove obsolete styling * Update pnpm lock file * Add Vue router * Resolve ESLint warnings * Add authorization view and controller * Use controller templating for route * A lot of stuff * A lot of stuff * Working * Set it all to vue 3 supported format * Fix import path * Add CORS policy * Realtime user data * Bugfixes * Add filter enrolment term function * Add terms to competence profile * Remove unused terms function * Added filter on professional outcomes and sorting * Removed unused things * Realtime user data * Add terms to competence profile * Cleanup code and improve competence profile converter * Filter on PostedAt value * Added frontend grid & colors --------- Co-authored-by: Tara Co-authored-by: Jelle Maas Co-authored-by: Sven Co-authored-by: Eline * Add decaying average calculations (#80) * initial version of the decaying average functionality * Added Professional development * Rename namespace to correspond with coding guidelines * Separate records into different files * Rename Value to Name to correspond with domain * Rename Value to Level to correspond with domain * Add professional skills to domain * Add short name to architectural layer and professional skills * Rename properties in competence profile random data filler * Rename CompetenceProfileOutcome to ProfessionalTaskOutcome * Add Professional skill outcome to competence profile * Use singular name * Use id references instead of name values * Use type keyword * re-adding decaying average files and logic * Add submissions from all courses from student to competence profile transformation * Fix merge issues * Add task and skill outcomes to competence profile return type * Fetch terms from canvas * Remove redundant imports * Change default application url * Prevent browser from launching on every restart * Cleanup code * Add filter enrolment term function * Add terms to competence profile * Remove unused terms function * Added filter on professional outcomes and sorting * Removed unused things * V0.1 * Defining colors for elements * Remove obsolete styling * Update pnpm lock file * Add Vue router * Resolve ESLint warnings * Add authorization view and controller * Use controller templating for route * A lot of stuff * A lot of stuff * Working * Set it all to vue 3 supported format * Fix import path * Add CORS policy * Realtime user data * Add terms to competence profile * Cleanup code and improve competence profile converter * Filter on PostedAt value * initial version of the decaying average functionality * re-adding decaying average files and logic * Decaying average calculation functionality supporting both ArchitectureLayer and Skill outcomes * Decaying average calculation functional per layer per activity and skills are independent of layers --------- Co-authored-by: jan.fojtik Co-authored-by: Tara Co-authored-by: Jelle Maas Co-authored-by: Sven Co-authored-by: Neal Geilen * Fix to not all learning outcomes getting retrieved (#82) * Attempt to fix not all learning outcomes getting retrieved * Use submission histories instead of rubric assessments directly * Ensure rubric assessments nodes has elements * Add performance dashboard frontend interface (#81) * Added Professional development * Rename namespace to correspond with coding guidelines * Separate records into different files * Rename Value to Name to correspond with domain * Rename Value to Level to correspond with domain * Add professional skills to domain * Add short name to architectural layer and professional skills * Rename properties in competence profile random data filler * Rename CompetenceProfileOutcome to ProfessionalTaskOutcome * Add Professional skill outcome to competence profile * Use singular name * Use id references instead of name values * Use type keyword * Add submissions from all courses from student to competence profile transformation * Fix merge issues * Add task and skill outcomes to competence profile return type * Fetch terms from canvas * Remove redundant imports * Change default application url * Prevent browser from launching on every restart * Cleanup code * V0.1 * Defining colors for elements * Remove obsolete styling * Update pnpm lock file * Add Vue router * Resolve ESLint warnings * Add authorization view and controller * Use controller templating for route * A lot of stuff * A lot of stuff * Working * Set it all to vue 3 supported format * Fix import path * Add CORS policy * Realtime user data * Bugfixes * Add filter enrolment term function * Add terms to competence profile * Remove unused terms function * Added filter on professional outcomes and sorting * Removed unused things * Realtime user data * Add terms to competence profile * Cleanup code and improve competence profile converter * Filter on PostedAt value * Added frontend grid & colors --------- Co-authored-by: Tara Co-authored-by: Jelle Maas Co-authored-by: Sven Co-authored-by: Eline * Add decaying average calculations (#80) * initial version of the decaying average functionality * Added Professional development * Rename namespace to correspond with coding guidelines * Separate records into different files * Rename Value to Name to correspond with domain * Rename Value to Level to correspond with domain * Add professional skills to domain * Add short name to architectural layer and professional skills * Rename properties in competence profile random data filler * Rename CompetenceProfileOutcome to ProfessionalTaskOutcome * Add Professional skill outcome to competence profile * Use singular name * Use id references instead of name values * Use type keyword * re-adding decaying average files and logic * Add submissions from all courses from student to competence profile transformation * Fix merge issues * Add task and skill outcomes to competence profile return type * Fetch terms from canvas * Remove redundant imports * Change default application url * Prevent browser from launching on every restart * Cleanup code * Add filter enrolment term function * Add terms to competence profile * Remove unused terms function * Added filter on professional outcomes and sorting * Removed unused things * V0.1 * Defining colors for elements * Remove obsolete styling * Update pnpm lock file * Add Vue router * Resolve ESLint warnings * Add authorization view and controller * Use controller templating for route * A lot of stuff * A lot of stuff * Working * Set it all to vue 3 supported format * Fix import path * Add CORS policy * Realtime user data * Add terms to competence profile * Cleanup code and improve competence profile converter * Filter on PostedAt value * initial version of the decaying average functionality * re-adding decaying average files and logic * Decaying average calculation functionality supporting both ArchitectureLayer and Skill outcomes * Decaying average calculation functional per layer per activity and skills are independent of layers --------- Co-authored-by: jan.fojtik Co-authored-by: Tara Co-authored-by: Jelle Maas Co-authored-by: Sven Co-authored-by: Neal Geilen * Correct HBO-i domain layer order * Add performance dashboard frontend interface (#81) * Added Professional development * Rename namespace to correspond with coding guidelines * Separate records into different files * Rename Value to Name to correspond with domain * Rename Value to Level to correspond with domain * Add professional skills to domain * Add short name to architectural layer and professional skills * Rename properties in competence profile random data filler * Rename CompetenceProfileOutcome to ProfessionalTaskOutcome * Add Professional skill outcome to competence profile * Use singular name * Use id references instead of name values * Use type keyword * Add submissions from all courses from student to competence profile transformation * Fix merge issues * Add task and skill outcomes to competence profile return type * Fetch terms from canvas * Remove redundant imports * Change default application url * Prevent browser from launching on every restart * Cleanup code * V0.1 * Defining colors for elements * Remove obsolete styling * Update pnpm lock file * Add Vue router * Resolve ESLint warnings * Add authorization view and controller * Use controller templating for route * A lot of stuff * A lot of stuff * Working * Set it all to vue 3 supported format * Fix import path * Add CORS policy * Realtime user data * Bugfixes * Add filter enrolment term function * Add terms to competence profile * Remove unused terms function * Added filter on professional outcomes and sorting * Removed unused things * Realtime user data * Add terms to competence profile * Cleanup code and improve competence profile converter * Filter on PostedAt value * Added frontend grid & colors --------- Co-authored-by: Tara Co-authored-by: Jelle Maas Co-authored-by: Sven Co-authored-by: Eline * Add decaying average calculations (#80) * initial version of the decaying average functionality * Added Professional development * Rename namespace to correspond with coding guidelines * Separate records into different files * Rename Value to Name to correspond with domain * Rename Value to Level to correspond with domain * Add professional skills to domain * Add short name to architectural layer and professional skills * Rename properties in competence profile random data filler * Rename CompetenceProfileOutcome to ProfessionalTaskOutcome * Add Professional skill outcome to competence profile * Use singular name * Use id references instead of name values * Use type keyword * re-adding decaying average files and logic * Add submissions from all courses from student to competence profile transformation * Fix merge issues * Add task and skill outcomes to competence profile return type * Fetch terms from canvas * Remove redundant imports * Change default application url * Prevent browser from launching on every restart * Cleanup code * Add filter enrolment term function * Add terms to competence profile * Remove unused terms function * Added filter on professional outcomes and sorting * Removed unused things * V0.1 * Defining colors for elements * Remove obsolete styling * Update pnpm lock file * Add Vue router * Resolve ESLint warnings * Add authorization view and controller * Use controller templating for route * A lot of stuff * A lot of stuff * Working * Set it all to vue 3 supported format * Fix import path * Add CORS policy * Realtime user data * Add terms to competence profile * Cleanup code and improve competence profile converter * Filter on PostedAt value * initial version of the decaying average functionality * re-adding decaying average files and logic * Decaying average calculation functionality supporting both ArchitectureLayer and Skill outcomes * Decaying average calculation functional per layer per activity and skills are independent of layers --------- Co-authored-by: jan.fojtik Co-authored-by: Tara Co-authored-by: Jelle Maas Co-authored-by: Sven Co-authored-by: Neal Geilen * Attempt to fix not all learning outcomes getting retrieved * Use submission histories instead of rubric assessments directly * Ensure rubric assessments nodes has elements * Correct HBO-i domain layer order --------- Co-authored-by: Jelle Maas Co-authored-by: Tara Co-authored-by: Sven Co-authored-by: Eline Co-authored-by: jendafojtik <83012368+jendafojtik@users.noreply.github.com> Co-authored-by: jan.fojtik * Initial component architecture for JSON and Word export * Added architecture for single point of component data retrieval and Word convertion * Typo * Cleanup component architecture and modify Word export to return OpenXML * Add support for dynamic component converters * Removed Word component merging logic from controller to WordDownloader class * Remve redundant method * Remove accidental document * Remove redundant models and interfaces * Cleanup Word downloader * Fix code analysis issues * Fix incorrect interface reference * Remove CLI projects and its dependencies * Rename Epsilon component classes * Reformat records * Remove obsolete test * Rename TResponse to TComponent * Remove redundant export data * Remove redundant interface * Add service collection extensions for components * Rework component converters to be included in component models * Move domain logic in controller to separate services * Move component name to attribute and rename to competence component * Remove duplicate package reference * Move HttpService class to Canvas project * Remove unused constants * Modify persona page to accommodate new component architecture (#91) * Created PersonaPage component for the new architecture. * fix feedback sven * Cleanup code * Modify architecture to force adding Word components instead of creating them * Add using statement to ensure stream is disposed --------- Co-authored-by: Jelle Maas * Add initial KPI matrix component (#87) * Created KpiMatrix component * Added modules and fixed comments * Fixed an issue where outcomes where duplicate * Created KPIMatrix in word document * Fixed all remaining issues. * Merge conflicts * Fixed MR requests * Changed course to allcourses * File restructure * Revised coding structure. Assignments that are not yest graded are also now included * Merged files in new structure * Colored rows * "Simplified" * Move HttpService class to Canvas project * Added legend and changed "GradeStatus" structure * Remove unused constants * Modify persona page to accommodate new component architecture (#91) * Created PersonaPage component for the new architecture. * fix feedback sven * Cleanup code * Modify architecture to force adding Word components instead of creating them * Add using statement to ensure stream is disposed --------- Co-authored-by: Jelle Maas * Move HttpService class to Canvas project * Remove unused constants * Modify persona page to accommodate new component architecture (#91) * Created PersonaPage component for the new architecture. * fix feedback sven * Cleanup code * Modify architecture to force adding Word components instead of creating them * Add using statement to ensure stream is disposed --------- Co-authored-by: Jelle Maas * Created KpiMatrix component * Added modules and fixed comments * Merge conflicts * File restructure * Revised coding structure. Assignments that are not yest graded are also now included * Created KpiMatrix component * Added modules and fixed comments * Merge conflicts * File restructure * Revised coding structure. Assignments that are not yest graded are also now included * Added legend and changed "GradeStatus" structure * Added extra grade type * Added legend to json result * Working order * startDate & endDate implementation for document generator * POC working * Created KpiMatrix component * Added modules and fixed comments * Fixed an issue where outcomes where duplicate * Created KPIMatrix in word document * Fixed all remaining issues. * Merge conflicts * Fixed MR requests * Changed course to allcourses * File restructure * Revised coding structure. Assignments that are not yest graded are also now included * Merged files in new structure * Colored rows * "Simplified" * Added legend and changed "GradeStatus" structure * Created KpiMatrix component * Added modules and fixed comments * Merge conflicts * File restructure * Revised coding structure. Assignments that are not yest graded are also now included * Created KpiMatrix component * Added modules and fixed comments * Merge conflicts * File restructure * Revised coding structure. Assignments that are not yest graded are also now included * Added legend and changed "GradeStatus" structure * Added extra grade type * Added legend to json result * Working order * startDate & endDate implementation for document generator * POC working * changes * Lint fixes * Lint fixes * Cleanup code and update coding guidelines * Change outcome grade statuses * Cleanup code --------- Co-authored-by: Neal Geilen Co-authored-by: Jelle Maas Co-authored-by: Koen Janssen <6256259+koen253janssen@users.noreply.github.com> --------- Co-authored-by: Sven Co-authored-by: Koen Janssen <6256259+koen253janssen@users.noreply.github.com> Co-authored-by: Sven Hansen <76601644+1SvenHansen@users.noreply.github.com> Co-authored-by: Sven Hansen <76601644+SyntaxSven@users.noreply.github.com> Co-authored-by: Tara Co-authored-by: Neal Geilen Co-authored-by: Eline Co-authored-by: jendafojtik <83012368+jendafojtik@users.noreply.github.com> Co-authored-by: jan.fojtik Co-authored-by: Jasper123pyah <73039915+Jasper123pyah@users.noreply.github.com> --- .editorconfig | 529 ++++++++++++ .github/workflows/continuous-integration.yml | 4 +- .../Component/CompetenceComponentFetcher.cs | 8 + .../CompetenceComponentNameAttribute.cs | 12 + .../Component/CompetenceProfile.cs | 36 + .../Component/ICompetenceComponent.cs | 8 + .../Component/ICompetenceComponentFetcher.cs | 12 + .../Component/ICompetenceProfileConverter.cs | 11 - .../Component/ICompetenceWordComponent.cs | 8 + .../Component/KpiMatrixAssignment.cs | 6 + .../Component/KpiMatrixCollection.cs | 196 +++++ .../Component/KpiMatrixOutcome.cs | 7 + .../Component/KpiMatrixOutcomeGradeStatus.cs | 6 + Epsilon.Abstractions/Component/PersonaPage.cs | 25 + .../Epsilon.Abstractions.csproj | 16 +- .../Export/ICanvasModuleExporter.cs | 8 - .../Export/IExportDataPackager.cs | 10 - Epsilon.Abstractions/Export/IExporter.cs | 8 - .../Export/IModuleExporterCollection.cs | 8 - Epsilon.Abstractions/Http/HttpService.cs | 8 - Epsilon.Abstractions/Model/Activity.cs | 6 +- .../Model/ArchitecturalLayer.cs | 7 +- .../Model/CompetenceProfile.cs | 12 - .../Model/CourseAssignment.cs | 15 +- Epsilon.Abstractions/Model/CourseModule.cs | 9 - .../Model/CourseModulePackage.cs | 8 + Epsilon.Abstractions/Model/CourseOutcome.cs | 15 +- Epsilon.Abstractions/Model/CoursePage.cs | 44 +- Epsilon.Abstractions/Model/ExportData.cs | 8 - Epsilon.Abstractions/Model/HboIDomain2018.cs | 42 +- .../Service/ICompetenceComponentService.cs | 14 + .../Service/ICompetenceDocumentService.cs | 6 + .../Epsilon.Canvas.Abstractions.csproj | 10 + .../ICanvasModuleCollectionFetcher.cs | 8 - .../Model/{Module.cs => CourseModule.cs} | 2 +- .../Model/EnrollmentTermCollection.cs | 3 +- .../Model/GraphQl/AssessmentRating.cs | 7 +- .../Model/GraphQl/Assignment.cs | 3 +- .../GraphQl/CanvasGraphQlQueryResponse.cs | 7 + .../Model/GraphQl/CanvasGraphQlSchema.cs | 8 + .../Model/GraphQl/Criteria.cs | 7 + .../Model/GraphQl/Criterion.cs | 2 +- .../Model/GraphQl/Outcome.cs | 3 +- .../Model/GraphQl/Rubric.cs | 7 + .../Model/GraphQl/RubricAssessmentNode.cs | 2 +- .../GraphQl/RubricAssessmentsConnection.cs | 2 +- .../GraphQl/SubmissionsConnectionNode.cs | 4 +- .../SubmissionsHistoriesConnectionNode.cs | 2 + .../Model/ModuleOutcomeResultCollection.cs | 2 +- Epsilon.Canvas.Abstractions/Model/Outcome.cs | 7 +- .../Model/OutcomeGradeStatus.cs | 9 + .../Model/OutcomeResultCollection.cs | 18 +- .../Model/OutcomeResultCollectionLink.cs | 7 +- .../Model/OutcomeResultLink.cs | 3 +- Epsilon.Canvas.Abstractions/Model/Page.cs | 4 +- .../GetAllUserCoursesSubmissionOutcomes.cs | 13 - .../GetUserCourseSubmissionOutcomes.cs | 13 - .../Service/IAccountHttpService.cs | 2 +- .../Service/IAssignmentHttpService.cs | 1 + .../Service/IFileHttpService.cs | 2 +- .../Service/IModuleHttpService.cs | 6 +- .../Service/IOutcomeHttpService.cs | 1 + .../Service/IPageHttpService.cs | 2 +- .../Service/IPaginatorHttpService.cs | 2 +- .../CanvasModuleCollectionFetcherTests.cs | 127 --- .../Epsilon.Canvas.Tests.csproj | 18 +- .../Services/AssignmentHttpServiceTests.cs | 73 +- .../CanvasModuleCollectionFetcher.cs | 49 -- .../CanvasServiceCollectionExtensions.cs | 3 - Epsilon.Canvas/CanvasSettings.cs | 2 +- .../Converter/LinkHeaderConverter.cs | 25 +- Epsilon.Canvas/Epsilon.Canvas.csproj | 22 +- Epsilon.Canvas/Http/HttpService.cs | 11 + Epsilon.Canvas/QueryConstants.cs | 66 +- Epsilon.Canvas/Service/AccountHttpService.cs | 10 +- .../Service/AssignmentHttpService.cs | 9 +- Epsilon.Canvas/Service/FileHttpService.cs | 14 +- Epsilon.Canvas/Service/GraphQlHttpService.cs | 18 +- Epsilon.Canvas/Service/ModuleHttpService.cs | 19 +- Epsilon.Canvas/Service/OutcomeHttpService.cs | 20 +- Epsilon.Canvas/Service/PageHttpService.cs | 19 +- .../Service/PaginatorHttpService.cs | 17 +- .../Service/SubmissionHttpService.cs | 7 +- Epsilon.Host.Cli/Epsilon.Host.Cli.csproj | 29 - Epsilon.Host.Cli/Program.cs | 44 - Epsilon.Host.Cli/Startup.cs | 115 --- Epsilon.Host.Frontend/pnpm-lock.yaml | 604 +++++++------- .../src/components/Competance/KPIMatrix.vue | 69 ++ .../src/components/Competance/KpiMatrix.vue | 100 --- .../src/components/Competance/KpiTable.vue | 84 -- .../src/components/KpiLegend/KpiLegend.vue | 67 -- Epsilon.Host.Frontend/src/logic/Api.ts | 782 +++++++++--------- .../src/views/PerformanceDashboard.vue | 56 +- Epsilon.Host.WebApi/CompetenceDocument.docx | 0 .../Controllers/ComponentController.cs | 35 +- .../Controllers/DocumentController.cs | 113 +-- .../Epsilon.Host.WebApi.csproj | 6 + Epsilon.Host.WebApi/Interfaces/IComponent.cs | 15 - Epsilon.Host.WebApi/Models/Document.cs | 7 - Epsilon.Host.WebApi/Models/Homepage.cs | 18 - Epsilon.Host.WebApi/Models/KpiMatrix.cs | 18 - Epsilon.Host.WebApi/Program.cs | 14 +- .../Responses/GetDocumentComponentResponse.cs | 8 - .../Responses/GetDocumentHomePageResponse.cs | 9 - .../Responses/GetDocumentKpiMatrixResponse.cs | 9 - .../Responses/GetDocumentStructureResponse.cs | 7 - Epsilon.Tests/Epsilon.Tests.csproj | 18 +- Epsilon.Tests/ExportDataPackagerTests.cs | 95 --- .../Exporters/WordModuleExporterTests.cs | 59 -- Epsilon.Tests/Usings.cs | 1 - Epsilon.sln | 10 +- ...etenceProfileCompetenceComponentFetcher.cs | 167 ++++ .../ComponentServiceCollectionExtensions.cs | 17 + .../Converters/CompetenceProfileConverter.cs | 105 --- .../Component/KpiMatrixComponentFetcher.cs | 137 +++ .../Component/PersonaPageComponentFetcher.cs | 66 ++ Epsilon/Constants.cs | 7 - Epsilon/Epsilon.csproj | 11 +- .../Exceptions/NoExportersFoundException.cs | 21 - Epsilon/Export/ExportDataPackager.cs | 82 -- Epsilon/Export/ExportOptions.cs | 13 - .../Export/Exporters/ConsoleModuleExporter.cs | 56 -- Epsilon/Export/Exporters/CsvModuleExporter.cs | 93 --- .../Export/Exporters/ExcelModuleExporter.cs | 114 --- .../Export/Exporters/WordModuleExporter.cs | 143 ---- Epsilon/Export/ModuleExporterCollection.cs | 40 - .../CoreServiceCollectionExtensions.cs | 36 - Epsilon/Service/CompetenceComponentService.cs | 57 ++ Epsilon/Service/CompetenceDocumentService.cs | 41 + global.json | 6 + 130 files changed, 2544 insertions(+), 2870 deletions(-) create mode 100644 .editorconfig create mode 100644 Epsilon.Abstractions/Component/CompetenceComponentFetcher.cs create mode 100644 Epsilon.Abstractions/Component/CompetenceComponentNameAttribute.cs create mode 100644 Epsilon.Abstractions/Component/CompetenceProfile.cs create mode 100644 Epsilon.Abstractions/Component/ICompetenceComponent.cs create mode 100644 Epsilon.Abstractions/Component/ICompetenceComponentFetcher.cs delete mode 100644 Epsilon.Abstractions/Component/ICompetenceProfileConverter.cs create mode 100644 Epsilon.Abstractions/Component/ICompetenceWordComponent.cs create mode 100644 Epsilon.Abstractions/Component/KpiMatrixAssignment.cs create mode 100644 Epsilon.Abstractions/Component/KpiMatrixCollection.cs create mode 100644 Epsilon.Abstractions/Component/KpiMatrixOutcome.cs create mode 100644 Epsilon.Abstractions/Component/KpiMatrixOutcomeGradeStatus.cs create mode 100644 Epsilon.Abstractions/Component/PersonaPage.cs delete mode 100644 Epsilon.Abstractions/Export/ICanvasModuleExporter.cs delete mode 100644 Epsilon.Abstractions/Export/IExportDataPackager.cs delete mode 100644 Epsilon.Abstractions/Export/IExporter.cs delete mode 100644 Epsilon.Abstractions/Export/IModuleExporterCollection.cs delete mode 100644 Epsilon.Abstractions/Http/HttpService.cs delete mode 100644 Epsilon.Abstractions/Model/CompetenceProfile.cs delete mode 100644 Epsilon.Abstractions/Model/CourseModule.cs create mode 100644 Epsilon.Abstractions/Model/CourseModulePackage.cs delete mode 100644 Epsilon.Abstractions/Model/ExportData.cs create mode 100644 Epsilon.Abstractions/Service/ICompetenceComponentService.cs create mode 100644 Epsilon.Abstractions/Service/ICompetenceDocumentService.cs delete mode 100644 Epsilon.Canvas.Abstractions/ICanvasModuleCollectionFetcher.cs rename Epsilon.Canvas.Abstractions/Model/{Module.cs => CourseModule.cs} (92%) create mode 100644 Epsilon.Canvas.Abstractions/Model/GraphQl/CanvasGraphQlQueryResponse.cs create mode 100644 Epsilon.Canvas.Abstractions/Model/GraphQl/CanvasGraphQlSchema.cs create mode 100644 Epsilon.Canvas.Abstractions/Model/GraphQl/Criteria.cs create mode 100644 Epsilon.Canvas.Abstractions/Model/GraphQl/Rubric.cs create mode 100644 Epsilon.Canvas.Abstractions/Model/OutcomeGradeStatus.cs delete mode 100644 Epsilon.Canvas.Abstractions/QueryResponse/GetAllUserCoursesSubmissionOutcomes.cs delete mode 100644 Epsilon.Canvas.Abstractions/QueryResponse/GetUserCourseSubmissionOutcomes.cs delete mode 100644 Epsilon.Canvas.Tests/CanvasModuleCollectionFetcherTests.cs delete mode 100644 Epsilon.Canvas/CanvasModuleCollectionFetcher.cs create mode 100644 Epsilon.Canvas/Http/HttpService.cs delete mode 100644 Epsilon.Host.Cli/Epsilon.Host.Cli.csproj delete mode 100644 Epsilon.Host.Cli/Program.cs delete mode 100644 Epsilon.Host.Cli/Startup.cs create mode 100644 Epsilon.Host.Frontend/src/components/Competance/KPIMatrix.vue delete mode 100644 Epsilon.Host.Frontend/src/components/Competance/KpiMatrix.vue delete mode 100644 Epsilon.Host.Frontend/src/components/Competance/KpiTable.vue delete mode 100644 Epsilon.Host.Frontend/src/components/KpiLegend/KpiLegend.vue delete mode 100644 Epsilon.Host.WebApi/CompetenceDocument.docx delete mode 100644 Epsilon.Host.WebApi/Interfaces/IComponent.cs delete mode 100644 Epsilon.Host.WebApi/Models/Document.cs delete mode 100644 Epsilon.Host.WebApi/Models/Homepage.cs delete mode 100644 Epsilon.Host.WebApi/Models/KpiMatrix.cs delete mode 100644 Epsilon.Host.WebApi/Responses/GetDocumentComponentResponse.cs delete mode 100644 Epsilon.Host.WebApi/Responses/GetDocumentHomePageResponse.cs delete mode 100644 Epsilon.Host.WebApi/Responses/GetDocumentKpiMatrixResponse.cs delete mode 100644 Epsilon.Host.WebApi/Responses/GetDocumentStructureResponse.cs delete mode 100644 Epsilon.Tests/ExportDataPackagerTests.cs delete mode 100644 Epsilon.Tests/Exporters/WordModuleExporterTests.cs create mode 100644 Epsilon/Component/CompetenceProfileCompetenceComponentFetcher.cs create mode 100644 Epsilon/Component/ComponentServiceCollectionExtensions.cs delete mode 100644 Epsilon/Component/Converters/CompetenceProfileConverter.cs create mode 100644 Epsilon/Component/KpiMatrixComponentFetcher.cs create mode 100644 Epsilon/Component/PersonaPageComponentFetcher.cs delete mode 100644 Epsilon/Constants.cs delete mode 100644 Epsilon/Export/Exceptions/NoExportersFoundException.cs delete mode 100644 Epsilon/Export/ExportDataPackager.cs delete mode 100644 Epsilon/Export/ExportOptions.cs delete mode 100644 Epsilon/Export/Exporters/ConsoleModuleExporter.cs delete mode 100644 Epsilon/Export/Exporters/CsvModuleExporter.cs delete mode 100644 Epsilon/Export/Exporters/ExcelModuleExporter.cs delete mode 100644 Epsilon/Export/Exporters/WordModuleExporter.cs delete mode 100644 Epsilon/Export/ModuleExporterCollection.cs delete mode 100644 Epsilon/Extensions/CoreServiceCollectionExtensions.cs create mode 100644 Epsilon/Service/CompetenceComponentService.cs create mode 100644 Epsilon/Service/CompetenceDocumentService.cs create mode 100644 global.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..2163fed3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,529 @@ +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = false +insert_final_newline = false +indent_size = 4 # A property with the same name was updated with a value 2 in a section [{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}] + +# Microsoft .NET properties +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async, file, required:error +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_var_elsewhere = true:error +csharp_style_var_for_built_in_types = true:error +csharp_style_var_when_type_is_apparent = true:error +dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_constants_rule.severity = error +dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style +dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols +dotnet_naming_rule.private_constants_rule_rule.import_to_resharper = True +dotnet_naming_rule.private_constants_rule_rule.resharper_description = private_constants_rule +dotnet_naming_rule.private_constants_rule_rule.resharper_guid = f52247c5-6592-45f7-9879-b6bfd6d8459b +dotnet_naming_rule.private_constants_rule_rule.severity = error +dotnet_naming_rule.private_constants_rule_rule.style = upper_camel_case_style +dotnet_naming_rule.private_constants_rule_rule.symbols = private_constants_rule_symbols +dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case_rule.import_to_resharper = True +dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case_rule.resharper_description = private_fields_must_begin_with_underscore_and_be_in_camel_case +dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case_rule.resharper_guid = d2f37c52-13db-42ab-8b2e-851bfee63f28 +dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case_rule.severity = error +dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case_rule.style = lower_camel_case_style_1 +dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case_rule.symbols = private_fields_must_begin_with_underscore_and_be_in_camel_case_symbols +dotnet_naming_rule.private_static_fields_override_rule_rule.import_to_resharper = True +dotnet_naming_rule.private_static_fields_override_rule_rule.resharper_description = private_static_fields_override_rule +dotnet_naming_rule.private_static_fields_override_rule_rule.resharper_guid = 870a7863-64c3-4e38-8349-bb38ac9bc098 +dotnet_naming_rule.private_static_fields_override_rule_rule.severity = error +dotnet_naming_rule.private_static_fields_override_rule_rule.style = upper_camel_case_style +dotnet_naming_rule.private_static_fields_override_rule_rule.symbols = private_static_fields_override_rule_symbols +dotnet_naming_rule.private_static_fields_rule.import_to_resharper = True +dotnet_naming_rule.private_static_fields_rule.resharper_description = PrivateStaticFields +dotnet_naming_rule.private_static_fields_rule.resharper_guid = 026d2708-e69b-4160-a912-ec0ef91fdf55 +dotnet_naming_rule.private_static_fields_rule.severity = error +dotnet_naming_rule.private_static_fields_rule.style = s_lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols +dotnet_naming_rule.private_static_fields_rule_1.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_fields_rule_1.severity = error +dotnet_naming_rule.private_static_fields_rule_1.style = s_lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule_1.symbols = private_static_fields_symbols_1 +dotnet_naming_rule.private_static_fields_rule_1_rule.import_to_resharper = True +dotnet_naming_rule.private_static_fields_rule_1_rule.resharper_description = private_static_fields_rule_1 +dotnet_naming_rule.private_static_fields_rule_1_rule.resharper_guid = 17a15a3a-af88-4159-ab8e-5652671e187a +dotnet_naming_rule.private_static_fields_rule_1_rule.severity = error +dotnet_naming_rule.private_static_fields_rule_1_rule.style = s_lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule_1_rule.symbols = private_static_fields_rule_1_symbols +dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_readonly_rule.severity = error +dotnet_naming_rule.private_static_readonly_rule.style = s_lower_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols +dotnet_naming_rule.private_static_readonly_rule_rule.import_to_resharper = True +dotnet_naming_rule.private_static_readonly_rule_rule.resharper_description = private_static_readonly_rule +dotnet_naming_rule.private_static_readonly_rule_rule.resharper_guid = 17a6af2e-e115-4369-bb84-acd48c562337 +dotnet_naming_rule.private_static_readonly_rule_rule.severity = error +dotnet_naming_rule.private_static_readonly_rule_rule.style = s_lower_camel_case_style +dotnet_naming_rule.private_static_readonly_rule_rule.symbols = private_static_readonly_rule_symbols +dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True +dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field +dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef +dotnet_naming_rule.unity_serialized_field_rule.severity = error +dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style +dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.lower_camel_case_style_1.capitalization = camel_case +dotnet_naming_style.lower_camel_case_style_1.required_prefix = _ +dotnet_naming_style.s_lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.s_lower_camel_case_style.required_prefix = s_ +dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case +dotnet_naming_symbols.private_constants_rule_symbols.applicable_accessibilities = local, private +dotnet_naming_symbols.private_constants_rule_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_rule_symbols.required_modifiers = const +dotnet_naming_symbols.private_constants_rule_symbols.resharper_applicable_kinds = constant_field +dotnet_naming_symbols.private_constants_rule_symbols.resharper_required_modifiers = any +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_symbols.required_modifiers = const +dotnet_naming_symbols.private_fields_must_begin_with_underscore_and_be_in_camel_case_symbols.applicable_accessibilities = local, private +dotnet_naming_symbols.private_fields_must_begin_with_underscore_and_be_in_camel_case_symbols.applicable_kinds = field +dotnet_naming_symbols.private_fields_must_begin_with_underscore_and_be_in_camel_case_symbols.resharper_applicable_kinds = any_field +dotnet_naming_symbols.private_fields_must_begin_with_underscore_and_be_in_camel_case_symbols.resharper_required_modifiers = any +dotnet_naming_symbols.private_static_fields_override_rule_symbols.applicable_accessibilities = local, private +dotnet_naming_symbols.private_static_fields_override_rule_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_override_rule_symbols.required_modifiers = const, static +dotnet_naming_symbols.private_static_fields_override_rule_symbols.resharper_applicable_kinds = constant_field +dotnet_naming_symbols.private_static_fields_override_rule_symbols.resharper_required_modifiers = static +dotnet_naming_symbols.private_static_fields_rule_1_symbols.applicable_accessibilities = local, private +dotnet_naming_symbols.private_static_fields_rule_1_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_rule_1_symbols.required_modifiers = static +dotnet_naming_symbols.private_static_fields_rule_1_symbols.resharper_applicable_kinds = any_field +dotnet_naming_symbols.private_static_fields_rule_1_symbols.resharper_required_modifiers = static +dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = local, private +dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static +dotnet_naming_symbols.private_static_fields_symbols.resharper_applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.resharper_required_modifiers = static +dotnet_naming_symbols.private_static_fields_symbols_1.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields_symbols_1.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols_1.required_modifiers = static +dotnet_naming_symbols.private_static_readonly_rule_symbols.applicable_accessibilities = local, private +dotnet_naming_symbols.private_static_readonly_rule_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_rule_symbols.required_modifiers = readonly, static +dotnet_naming_symbols.private_static_readonly_rule_symbols.resharper_applicable_kinds = readonly_field +dotnet_naming_symbols.private_static_readonly_rule_symbols.resharper_required_modifiers = static +dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:none +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:none +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:error +dotnet_style_qualification_for_event = false:error +dotnet_style_qualification_for_field = false:error +dotnet_style_qualification_for_method = false:error +dotnet_style_qualification_for_property = false:error +dotnet_style_require_accessibility_modifiers = for_non_interface_members:error + +# ReSharper properties +resharper_apply_auto_detected_rules = false +resharper_blank_lines_after_control_transfer_statements = 1 +resharper_braces_for_for = required +resharper_braces_for_foreach = required +resharper_braces_for_ifelse = required +resharper_braces_for_while = required +resharper_braces_redundant = false +resharper_csharp_blank_lines_around_single_line_invocable = 1 +resharper_csharp_case_block_braces = next_line_shifted_2 +resharper_csharp_max_line_length = 176 +resharper_csharp_remove_blank_lines_near_braces_in_declarations = false +resharper_csharp_wrap_after_declaration_lpar = true +resharper_csharp_wrap_before_declaration_rpar = true +resharper_csharp_wrap_before_first_type_parameter_constraint = true +resharper_csharp_wrap_extends_list_style = chop_if_long +resharper_csharp_wrap_parameters_style = chop_if_long +resharper_csharp_wrap_ternary_expr_style = chop_always +resharper_enforce_line_ending_style = true +resharper_formatter_off_tag = @formatter:off +resharper_formatter_on_tag = @formatter:on +resharper_formatter_tags_enabled = true +resharper_instance_members_qualify_declared_in = +resharper_keep_existing_initializer_arrangement = false +resharper_max_attribute_length_for_same_line = 64 +resharper_new_line_before_while = true +resharper_object_creation_when_type_evident = explicitly_typed +resharper_parentheses_redundancy_style = remove +resharper_parentheses_same_type_operations = true +resharper_place_accessorholder_attribute_on_same_line = false +resharper_place_accessor_attribute_on_same_line = false +resharper_place_constructor_initializer_on_same_line = false +resharper_place_expr_method_on_single_line = true +resharper_place_expr_property_on_single_line = true +resharper_place_record_field_attribute_on_same_line = true +resharper_place_simple_embedded_statement_on_same_line = false +resharper_place_simple_initializer_on_single_line = false +resharper_show_autodetect_configure_formatting_tip = false +resharper_trailing_comma_in_multiline_lists = true +resharper_trailing_comma_in_singleline_lists = true +resharper_use_indent_from_vs = false +resharper_wrap_array_initializer_style = chop_always +resharper_wrap_before_eq = true +resharper_wrap_object_and_collection_initializer_style = chop_always +resharper_wrap_property_pattern = chop_always + +# ReSharper inspection severities +resharper_arrange_null_checking_pattern_highlighting = error +resharper_arrange_object_creation_when_type_evident_highlighting = error +resharper_arrange_object_creation_when_type_not_evident_highlighting = error +resharper_arrange_redundant_parentheses_highlighting = error +resharper_arrange_trailing_comma_in_multiline_lists_highlighting = error +resharper_arrange_trailing_comma_in_singleline_lists_highlighting = error +resharper_arrange_var_keywords_in_deconstructing_declaration_highlighting = error +resharper_bad_linq_line_breaks_highlighting = error +resharper_enforce_foreach_statement_braces_highlighting = error +resharper_enforce_if_statement_braces_highlighting = error +resharper_inconsistent_naming_highlighting = error +resharper_invert_if_highlighting = none +resharper_lambda_expression_can_be_made_static_highlighting = error +resharper_loop_can_be_converted_to_query_highlighting = none +resharper_method_supports_cancellation_highlighting = error +resharper_suggest_discard_declaration_var_style_highlighting = suggestion + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 # A property with the same name was updated with a value 4 in a section [*]; with a value 4 in a section [*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}] + +# JSON files +[*.json] +indent_size = 2 # A property with the same name was updated with a value 4 in a section [*]; with a value 4 in a section [*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}] + +[*.cs] +# Prefer method-like constructs to have a block body, except for lambdas +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none +csharp_style_expression_bodied_local_functions = false:none +csharp_style_expression_bodied_lambdas = true:none + + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:error +csharp_style_pattern_matching_over_is_with_cast_check = true:error +csharp_style_prefer_switch_expression = true:error + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:error + +# Modifier preferences +csharp_prefer_static_local_function = true:error +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:error + +# Code-block preferences +csharp_prefer_braces = when_multiline:error +csharp_prefer_simple_using_statement = true:error + +# Expression-level preferences +csharp_style_unused_value_assignment_preference = discard_variable:error +csharp_prefer_simple_default_expression = true:error +csharp_style_deconstructed_variable_declaration = true:error +csharp_style_inlined_variable_declaration = true:error +csharp_style_pattern_local_over_anonymous_function = true:error +csharp_style_prefer_index_operator = true:error +csharp_style_prefer_range_operator = true:error +csharp_style_throw_expression = true:error +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:error + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true # A property with the same name was updated with a value false in a section [*] +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = no_change +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false # A property with the same name was updated with a value true in a section [*] +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false +dotnet_naming_rule.private_constants_rule.severity = error +dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style +dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols +dotnet_naming_rule.private_static_fields_override_rule.severity = error +dotnet_naming_rule.private_static_fields_override_rule.style = upper_camel_case_style +dotnet_naming_rule.private_static_fields_override_rule.symbols = private_static_fields_override_symbols +dotnet_naming_rule.private_static_fields_rule.severity = error +dotnet_naming_rule.private_static_fields_rule.style = s_lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols +dotnet_naming_rule.private_static_fields_rule_1.severity = error +dotnet_naming_rule.private_static_fields_rule_1.style = s_lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule_1.symbols = private_static_fields_symbols_1 +dotnet_naming_rule.private_static_readonly_rule.severity = error +dotnet_naming_rule.private_static_readonly_rule.style = s_lower_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols +dotnet_naming_rule.unity_serialized_field_rule.severity = error +dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style +dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols +dotnet_naming_rule.unity_serialized_field_rule_1.severity = error +dotnet_naming_rule.unity_serialized_field_rule_1.style = lower_camel_case_style +dotnet_naming_rule.unity_serialized_field_rule_1.symbols = unity_serialized_field_symbols_1 +dotnet_naming_rule.unity_serialized_field_rule_2.severity = error +dotnet_naming_rule.unity_serialized_field_rule_2.style = lower_camel_case_style +dotnet_naming_rule.unity_serialized_field_rule_2.symbols = unity_serialized_field_symbols_2 +dotnet_naming_rule.unity_serialized_field_rule_3.severity = error +dotnet_naming_rule.unity_serialized_field_rule_3.style = lower_camel_case_style +dotnet_naming_rule.unity_serialized_field_rule_3.symbols = unity_serialized_field_symbols_3 +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.s_lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.s_lower_camel_case_style.required_prefix = s_ +dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_symbols.required_modifiers = const +dotnet_naming_symbols.private_static_fields_override_symbols.applicable_accessibilities = local, private +dotnet_naming_symbols.private_static_fields_override_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_override_symbols.required_modifiers = const, static +dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static +dotnet_naming_symbols.private_static_fields_symbols_1.applicable_accessibilities = local, private +dotnet_naming_symbols.private_static_fields_symbols_1.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols_1.required_modifiers = static +dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private +dotnet_naming_style.require_underscore_prefix_and_camel_case.required_prefix = _ +dotnet_naming_style.require_underscore_prefix_and_camel_case.capitalization = camel_case +dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case.symbols = private_fields +dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case.style = require_underscore_prefix_and_camel_case +dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case.severity = error +dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_accessibilities = * +dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols_2.applicable_accessibilities = * +dotnet_naming_symbols.unity_serialized_field_symbols_2.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols_3.applicable_accessibilities = * +dotnet_naming_symbols.unity_serialized_field_symbols_3.applicable_kinds = +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false +resharper_csharp_naming_rule.private_constants = AaBb +resharper_csharp_naming_rule.private_static_fields = s_ + aaBb +resharper_csharp_naming_rule.private_static_readonly = s_ + aaBb +resharper_enforce_line_ending_style = true +resharper_formatter_off_tag = @formatter:off +resharper_formatter_on_tag = @formatter:on +resharper_formatter_tags_enabled = true +resharper_place_record_field_attribute_on_same_line = true +resharper_show_autodetect_configure_formatting_tip = false +resharper_use_indent_from_vs = false +resharper_csharp_place_constructor_initializer_on_same_line = false +resharper_arrange_null_checking_pattern_highlighting = error +resharper_arrange_object_creation_when_type_evident_highlighting = error +resharper_arrange_object_creation_when_type_not_evident_highlighting = error +resharper_arrange_redundant_parentheses_highlighting = error +resharper_arrange_this_qualifier_highlighting = error +resharper_arrange_trailing_comma_in_multiline_lists_highlighting = error +resharper_arrange_trailing_comma_in_singleline_lists_highlighting = error +resharper_arrange_type_member_modifiers_highlighting = error +resharper_arrange_type_modifiers_highlighting = error +resharper_arrange_var_keywords_in_deconstructing_declaration_highlighting = error +resharper_bad_linq_line_breaks_highlighting = error +resharper_built_in_type_reference_style_for_member_access_highlighting = error +resharper_built_in_type_reference_style_highlighting = error +resharper_enforce_foreach_statement_braces_highlighting = error +resharper_enforce_if_statement_braces_highlighting = error +resharper_inconsistent_naming_highlighting = error +resharper_invert_if_highlighting = none +resharper_lambda_expression_can_be_made_static_highlighting = error +resharper_loop_can_be_converted_to_query_highlighting = none +resharper_redundant_base_qualifier_highlighting = error +resharper_suggest_discard_declaration_var_style_highlighting = suggestion +resharper_suggest_var_or_type_built_in_types_highlighting = error +resharper_suggest_var_or_type_elsewhere_highlighting = error +resharper_suggest_var_or_type_simple_types_highlighting = error +resharper_web_config_module_not_resolved_highlighting = error +resharper_web_config_type_not_resolved_highlighting = error +resharper_web_config_wrong_module_highlighting = error + +### Configuration for .Net analyzers executed on this repo ### +[*.{cs,vb}] +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_readonly_field = true:error +dotnet_code_quality_unused_parameters = all:error +dotnet_style_coalesce_expression = true:error +dotnet_style_collection_initializer = true:error +dotnet_style_explicit_tuple_names = true:error +dotnet_style_null_propagation = true:error +dotnet_style_object_initializer = true:error +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:error +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:error +dotnet_style_prefer_inferred_tuple_names = true:error +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error +dotnet_style_prefer_simplified_interpolation = true:error + +# CA1305: Pass IFormatProvider - https://github.com/dotnet/roslyn-analyzers/issues/6379 +dotnet_diagnostic.ca1305.severity = error + +# CA1851: Possible multiple enumerations of 'IEnumerable' collection - https://github.com/dotnet/roslyn-analyzers/issues/6379 +dotnet_diagnostic.ca1851.severity = error + +# CA1014: Mark assemblies with CLSCompliantAttribute +dotnet_diagnostic.ca1014.severity = none + +# CA1707: Identifiers should not contain underscores +dotnet_diagnostic.ca1707.severity = none + +# CA1711: Identifiers should not have incorrect suffix +dotnet_diagnostic.ca1711.severity = none + +# CA1716: Identifiers should not match keywords +dotnet_diagnostic.ca1716.severity = none + +# CA1062: Validate arguments of public methods +dotnet_diagnostic.ca1062.severity = none + +# CA2007: Do not directly await a Task +dotnet_diagnostic.ca2007.severity = none + +# CA1848: Use the LoggerMessage delegates +dotnet_diagnostic.ca1848.severity = none + +# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly. +dotnet_diagnostic.ca1826.severity = none + +# Code style errors +dotnet_diagnostic.ide0001.severity = error +dotnet_diagnostic.ide0002.severity = error +dotnet_diagnostic.ide0004.severity = error +dotnet_diagnostic.ide0005.severity = error +dotnet_diagnostic.ide0007.severity = error +dotnet_diagnostic.ide0008.severity = error +dotnet_diagnostic.ide0010.severity = error +dotnet_diagnostic.ide0011.severity = error +dotnet_diagnostic.ide0016.severity = error +dotnet_diagnostic.ide0017.severity = error +dotnet_diagnostic.ide0018.severity = error +dotnet_diagnostic.ide0019.severity = error +dotnet_diagnostic.ide0020.severity = error +dotnet_diagnostic.ide0021.severity = error +dotnet_diagnostic.ide0022.severity = error +dotnet_diagnostic.ide0023.severity = error +dotnet_diagnostic.ide0024.severity = error +dotnet_diagnostic.ide0025.severity = error +dotnet_diagnostic.ide0026.severity = error +dotnet_diagnostic.ide0027.severity = error +dotnet_diagnostic.ide0028.severity = error +dotnet_diagnostic.ide0029.severity = error +dotnet_diagnostic.ide0030.severity = error +dotnet_diagnostic.ide0031.severity = error +dotnet_diagnostic.ide0032.severity = error +dotnet_diagnostic.ide0033.severity = error +dotnet_diagnostic.ide0034.severity = error +dotnet_diagnostic.ide0035.severity = error +dotnet_diagnostic.ide0036.severity = error +dotnet_diagnostic.ide0037.severity = error +dotnet_diagnostic.ide0038.severity = error +dotnet_diagnostic.ide0039.severity = error +dotnet_diagnostic.ide0040.severity = error +dotnet_diagnostic.ide0041.severity = error +dotnet_diagnostic.ide0042.severity = error +dotnet_diagnostic.ide0044.severity = error +dotnet_diagnostic.ide0045.severity = error +dotnet_diagnostic.ide0046.severity = none +dotnet_diagnostic.ide0047.severity = error +dotnet_diagnostic.ide0048.severity = error +dotnet_diagnostic.ide0049.severity = error +dotnet_diagnostic.ide0050.severity = error +dotnet_diagnostic.ide0051.severity = error +dotnet_diagnostic.ide0052.severity = error +dotnet_diagnostic.ide0053.severity = error +dotnet_diagnostic.ide0054.severity = error +dotnet_diagnostic.ide0056.severity = error +dotnet_diagnostic.ide0057.severity = error +dotnet_diagnostic.ide0059.severity = error +dotnet_diagnostic.ide0060.severity = error +dotnet_diagnostic.ide0061.severity = error +dotnet_diagnostic.ide0062.severity = error +dotnet_diagnostic.ide0063.severity = error +dotnet_diagnostic.ide0064.severity = error +dotnet_diagnostic.ide0065.severity = error +dotnet_diagnostic.ide0065.severity = error +dotnet_diagnostic.ide0066.severity = error +dotnet_diagnostic.ide0070.severity = error +dotnet_diagnostic.ide0071.severity = error +dotnet_diagnostic.ide0072.severity = error +dotnet_diagnostic.ide0073.severity = error +dotnet_diagnostic.ide0074.severity = error +dotnet_diagnostic.ide0075.severity = error +dotnet_diagnostic.ide0076.severity = error +dotnet_diagnostic.ide0077.severity = error +dotnet_diagnostic.ide0078.severity = error +dotnet_diagnostic.ide0079.severity = error +dotnet_diagnostic.ide0080.severity = error +dotnet_diagnostic.ide0081.severity = error +dotnet_diagnostic.ide0082.severity = error +dotnet_diagnostic.ide0083.severity = error +dotnet_diagnostic.ide0084.severity = error +dotnet_diagnostic.ide0100.severity = error +dotnet_diagnostic.ide0110.severity = error +dotnet_diagnostic.ide0130.severity = error +dotnet_diagnostic.ide0140.severity = error +dotnet_diagnostic.ide0150.severity = error +dotnet_diagnostic.ide0161.severity = error +dotnet_diagnostic.ide0170.severity = error +dotnet_diagnostic.ide0180.severity = error +dotnet_diagnostic.ide1005.severity = error +dotnet_diagnostic.ide1006.severity = error + +[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}] +# A property with the same name was updated with a value 2 in a section [{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}] +tab_width = 4 +indent_style = space +indent_size = 4 diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 64a46248..431cfbaa 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - + - name: Setup .NET uses: actions/setup-dotnet@v1.7.2 with: @@ -25,7 +25,7 @@ jobs: - name: Build run: dotnet build --no-restore - + test: name: Test needs: [ setup, build ] diff --git a/Epsilon.Abstractions/Component/CompetenceComponentFetcher.cs b/Epsilon.Abstractions/Component/CompetenceComponentFetcher.cs new file mode 100644 index 00000000..0ee2a961 --- /dev/null +++ b/Epsilon.Abstractions/Component/CompetenceComponentFetcher.cs @@ -0,0 +1,8 @@ +namespace Epsilon.Abstractions.Component; + +public abstract class CompetenceComponentFetcher : ICompetenceComponentFetcher where TComponent : ICompetenceComponent +{ + public async Task FetchUnknown(DateTime startDate, DateTime endDate) => await Fetch(startDate, endDate); + + public abstract Task Fetch(DateTime startDate, DateTime endDate); +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/CompetenceComponentNameAttribute.cs b/Epsilon.Abstractions/Component/CompetenceComponentNameAttribute.cs new file mode 100644 index 00000000..5169c5cc --- /dev/null +++ b/Epsilon.Abstractions/Component/CompetenceComponentNameAttribute.cs @@ -0,0 +1,12 @@ +namespace Epsilon.Abstractions.Component; + +[AttributeUsage(AttributeTargets.Class)] +public sealed class CompetenceComponentNameAttribute : Attribute +{ + public CompetenceComponentNameAttribute(string name) + { + Name = name; + } + + public string Name { get; } +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/CompetenceProfile.cs b/Epsilon.Abstractions/Component/CompetenceProfile.cs new file mode 100644 index 00000000..f62c2615 --- /dev/null +++ b/Epsilon.Abstractions/Component/CompetenceProfile.cs @@ -0,0 +1,36 @@ +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; +using Epsilon.Abstractions.Model; +using Epsilon.Canvas.Abstractions.Model; + +namespace Epsilon.Abstractions.Component; + +[CompetenceComponentName("competence_profile")] +public record CompetenceProfile( + IHboIDomain HboIDomain, + IEnumerable ProfessionalTaskOutcomes, + IEnumerable ProfessionalSkillOutcomes, + IEnumerable Terms, + IEnumerable DecayingAveragesPerTask, + IEnumerable DecayingAveragesPerSkill +) : ICompetenceWordComponent +{ + public void AddToWordDocument(MainDocumentPart mainDocumentPart) + { + // TODO: This is simply an example to show the capability of the component architecture + var body = new Body(); + + foreach (var enrollmentTerm in Terms) + { + body.AppendChild( + new Paragraph( + new Run( + new Text(enrollmentTerm.Name) + ) + ) + ); + } + + mainDocumentPart.Document.AppendChild(body); + } +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/ICompetenceComponent.cs b/Epsilon.Abstractions/Component/ICompetenceComponent.cs new file mode 100644 index 00000000..5f3da0eb --- /dev/null +++ b/Epsilon.Abstractions/Component/ICompetenceComponent.cs @@ -0,0 +1,8 @@ +namespace Epsilon.Abstractions.Component; + +#pragma warning disable CA1040 +public interface ICompetenceComponent +#pragma warning restore CA1040 +{ + +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/ICompetenceComponentFetcher.cs b/Epsilon.Abstractions/Component/ICompetenceComponentFetcher.cs new file mode 100644 index 00000000..dde1871c --- /dev/null +++ b/Epsilon.Abstractions/Component/ICompetenceComponentFetcher.cs @@ -0,0 +1,12 @@ +namespace Epsilon.Abstractions.Component; + +public interface ICompetenceComponentFetcher +{ + public Task FetchUnknown(DateTime startDate, DateTime endDate); +} + +public interface ICompetenceComponentFetcher : ICompetenceComponentFetcher + where TComponent : ICompetenceComponent +{ + public Task Fetch(DateTime startDate, DateTime endDate); +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/ICompetenceProfileConverter.cs b/Epsilon.Abstractions/Component/ICompetenceProfileConverter.cs deleted file mode 100644 index 12bb7b0d..00000000 --- a/Epsilon.Abstractions/Component/ICompetenceProfileConverter.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Epsilon.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Model.GraphQl; -using Epsilon.Canvas.Abstractions.QueryResponse; - -namespace Epsilon.Abstractions.Component; - -public interface ICompetenceProfileConverter -{ - public CompetenceProfile ConvertFrom(GetAllUserCoursesSubmissionOutcomes getAllUserCoursesSubmissionOutcomes,IHboIDomain domain, IEnumerable terms); -} \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/ICompetenceWordComponent.cs b/Epsilon.Abstractions/Component/ICompetenceWordComponent.cs new file mode 100644 index 00000000..29cfca03 --- /dev/null +++ b/Epsilon.Abstractions/Component/ICompetenceWordComponent.cs @@ -0,0 +1,8 @@ +using DocumentFormat.OpenXml.Packaging; + +namespace Epsilon.Abstractions.Component; + +public interface ICompetenceWordComponent : ICompetenceComponent +{ + public void AddToWordDocument(MainDocumentPart mainDocumentPart); +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/KpiMatrixAssignment.cs b/Epsilon.Abstractions/Component/KpiMatrixAssignment.cs new file mode 100644 index 00000000..625815fa --- /dev/null +++ b/Epsilon.Abstractions/Component/KpiMatrixAssignment.cs @@ -0,0 +1,6 @@ +namespace Epsilon.Abstractions.Component; + +public record KpiMatrixAssignment( + string Name, + IEnumerable Outcomes +); \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/KpiMatrixCollection.cs b/Epsilon.Abstractions/Component/KpiMatrixCollection.cs new file mode 100644 index 00000000..c58cf57a --- /dev/null +++ b/Epsilon.Abstractions/Component/KpiMatrixCollection.cs @@ -0,0 +1,196 @@ +using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; +using Epsilon.Canvas.Abstractions.Model; + +namespace Epsilon.Abstractions.Component; + +[CompetenceComponentName("kpi_matrix")] +public record KpiMatrixCollection( + IEnumerable KpiMatrixAssignments, + IDictionary GradeStatus +) : ICompetenceWordComponent +{ + public static readonly IDictionary DefaultGradeStatus = new Dictionary + { + { + OutcomeGradeStatus.Mastered, new KpiMatrixOutcomeGradeStatus("Mastered", "44F656") + }, + { + OutcomeGradeStatus.NotMastered, new KpiMatrixOutcomeGradeStatus("Insufficient", "FA1818") + }, + { + OutcomeGradeStatus.NotGraded, new KpiMatrixOutcomeGradeStatus("Not applicable", "FAFF00") + }, + { + OutcomeGradeStatus.NotAssessed, new KpiMatrixOutcomeGradeStatus("Needs grade", "9F2B68") + }, + }; + + public void AddToWordDocument(MainDocumentPart mainDocumentPart) + { + var body = new Body(); + // Create a table, with rows for the outcomes and columns for the assignments. + var table = new Table(); + + var assignments = KpiMatrixAssignments.OrderBy(static ass => ass.Name).ToList(); + + // Set table properties for formatting. + table.AppendChild(new TableProperties( + new TableWidth + { + Width = "0", Type = TableWidthUnitValues.Auto, + })); + + // Calculate the header row height based on the longest assignment name. + var headerRowHeight = 0; + if (KpiMatrixAssignments.Any()) + { + headerRowHeight = assignments.Max(static a => a.Name.Length) * 111; + } + + // Create the table header row. + var headerRow = new TableRow(); + headerRow.AppendChild(new TableRowProperties(new TableRowHeight + { + Val = (UInt32Value)(uint)headerRowHeight, + })); + + // Empty top-left cell. + headerRow.AppendChild(CreateTableCellWithBorders("2500", new Paragraph(new Run(new Text(""))))); + + foreach (var assignment in assignments) + { + var cell = CreateTableCellWithBorders("100"); + cell.FirstChild.Append(new TextDirection + { + Val = TextDirectionValues.TopToBottomLeftToRightRotated, + }); + + cell.Append(new Paragraph(new Run(new Text(assignment.Name)))); + cell.FirstChild.Append(new Shading + { + Fill = assignments.IndexOf(assignment) % 2 == 0 + ? "FFFFFF" + : "d3d3d3", + }); + headerRow.AppendChild(cell); + } + + table.AppendChild(headerRow); + + var listOfOutcomes = new Dictionary(); + foreach (var assignment in KpiMatrixAssignments) + { + foreach (var outcome in assignment.Outcomes) + { + listOfOutcomes.TryAdd(outcome.Id, outcome); + } + } + + // Add the outcome rows. + foreach (var outcome in listOfOutcomes.OrderByDescending(static o => o.Value.Title)) + { + var row = new TableRow(); + + // Add the outcome title cell. + row.AppendChild(CreateTableCellWithBorders("2500", new Paragraph(new Run(new Text(outcome.Value.Title))))); + + // Add the assignment cells. + foreach (var assignment in assignments) + { + var outcomeAssignment = assignment.Outcomes.FirstOrDefault(o => o.Id == outcome.Key); + var cell = CreateTableCellWithBorders("100"); + + // Set cell color based on GradeStatus. + var fillColor = outcomeAssignment != null + ? outcomeAssignment.GradeStatus.Color + : assignments.IndexOf(assignment) % 2 == 0 + ? "ffffff" + : "d3d3d3"; + + cell.FirstChild?.Append(new Shading + { + Fill = fillColor, + }); + + // Add an empty text element since we're using color instead of text. + cell.Append(new Paragraph(new Run(new Text("")))); + row.AppendChild(cell); + } + + table.AppendChild(row); + } + + body.AppendChild(GetLegend()); + body.Append(new Paragraph(new Run(new Text("")))); + body.AppendChild(table); + + mainDocumentPart.Document.AppendChild(body); + } + + private OpenXmlElement GetLegend() + { + var table = new Table(); + foreach (var status in GradeStatus) + { + var row = new TableRow(); + var cellName = CreateTableCellWithBorders("200"); + cellName.Append(new Paragraph(new Run(new Text(status.Value.Status)))); + + var cellValue = CreateTableCellWithBorders("200"); + cellValue.Append(new Paragraph(new Run(new Text("")))); + cellValue.FirstChild?.Append(new Shading + { + Fill = status.Value.Color, + }); + row.AppendChild(cellName); + row.AppendChild(cellValue); + table.AppendChild(row); + } + + return table; + } + + + private static TableCell CreateTableCellWithBorders(string? width, params OpenXmlElement[] elements) + { + var cell = new TableCell(); + var cellProperties = new TableCellProperties(); + var borders = new TableCellBorders( + new LeftBorder + { + Val = BorderValues.Single, + }, + new RightBorder + { + Val = BorderValues.Single, + }, + new TopBorder + { + Val = BorderValues.Single, + }, + new BottomBorder + { + Val = BorderValues.Single, + }); + + foreach (var element in elements) + { + cell.Append(element); + } + + if (width != null) + { + cellProperties.Append(new TableCellWidth + { + Type = TableWidthUnitValues.Dxa, Width = width, + }); + } + + cellProperties.Append(borders); + cell.PrependChild(cellProperties); + + return cell; + } +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/KpiMatrixOutcome.cs b/Epsilon.Abstractions/Component/KpiMatrixOutcome.cs new file mode 100644 index 00000000..90ed26c4 --- /dev/null +++ b/Epsilon.Abstractions/Component/KpiMatrixOutcome.cs @@ -0,0 +1,7 @@ + +namespace Epsilon.Abstractions.Component; +public record KpiMatrixOutcome( + int Id, + string Title, + KpiMatrixOutcomeGradeStatus GradeStatus +); \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/KpiMatrixOutcomeGradeStatus.cs b/Epsilon.Abstractions/Component/KpiMatrixOutcomeGradeStatus.cs new file mode 100644 index 00000000..f1a47c1d --- /dev/null +++ b/Epsilon.Abstractions/Component/KpiMatrixOutcomeGradeStatus.cs @@ -0,0 +1,6 @@ +namespace Epsilon.Abstractions.Component; + +public record KpiMatrixOutcomeGradeStatus( + string Status, + string Color +); \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/PersonaPage.cs b/Epsilon.Abstractions/Component/PersonaPage.cs new file mode 100644 index 00000000..a936bf6f --- /dev/null +++ b/Epsilon.Abstractions/Component/PersonaPage.cs @@ -0,0 +1,25 @@ +using System.Text; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; + +namespace Epsilon.Abstractions.Component; + +[CompetenceComponentName("persona")] +public record PersonaPage(string PersonaHtml) : ICompetenceWordComponent +{ + public void AddToWordDocument(MainDocumentPart mainDocumentPart) + { + var personaHtmlBuffer = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes($"{PersonaHtml}")).ToArray(); + using var personaHtmlStream = new MemoryStream(personaHtmlBuffer); + + var formatImportPart = mainDocumentPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.Html); + formatImportPart.FeedData(personaHtmlStream); + + mainDocumentPart.Document.AppendChild(new Body( + new AltChunk + { + Id = mainDocumentPart.GetIdOfPart(formatImportPart), + } + )); + } +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Epsilon.Abstractions.csproj b/Epsilon.Abstractions/Epsilon.Abstractions.csproj index d3a8bb24..d46fbc80 100644 --- a/Epsilon.Abstractions/Epsilon.Abstractions.csproj +++ b/Epsilon.Abstractions/Epsilon.Abstractions.csproj @@ -4,10 +4,24 @@ net6.0 enable enable + latest-All + true + true - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/Epsilon.Abstractions/Export/ICanvasModuleExporter.cs b/Epsilon.Abstractions/Export/ICanvasModuleExporter.cs deleted file mode 100644 index c9ec6179..00000000 --- a/Epsilon.Abstractions/Export/ICanvasModuleExporter.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Epsilon.Abstractions.Model; - -namespace Epsilon.Abstractions.Export; - -public interface ICanvasModuleExporter : IExporter -{ - -} \ No newline at end of file diff --git a/Epsilon.Abstractions/Export/IExportDataPackager.cs b/Epsilon.Abstractions/Export/IExportDataPackager.cs deleted file mode 100644 index 79dd138e..00000000 --- a/Epsilon.Abstractions/Export/IExportDataPackager.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Epsilon.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Model; - -namespace Epsilon.Abstractions.Export; - - -public interface IExportDataPackager -{ - public Task GetExportData(IAsyncEnumerable data); -} \ No newline at end of file diff --git a/Epsilon.Abstractions/Export/IExporter.cs b/Epsilon.Abstractions/Export/IExporter.cs deleted file mode 100644 index 5f049fff..00000000 --- a/Epsilon.Abstractions/Export/IExporter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Epsilon.Abstractions.Export; - -public interface IExporter -{ - public IEnumerable Formats { get; } - public string FileExtension { get; } - Task Export(T data, string format); -} \ No newline at end of file diff --git a/Epsilon.Abstractions/Export/IModuleExporterCollection.cs b/Epsilon.Abstractions/Export/IModuleExporterCollection.cs deleted file mode 100644 index abba4749..00000000 --- a/Epsilon.Abstractions/Export/IModuleExporterCollection.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Epsilon.Abstractions.Export; - -public interface IModuleExporterCollection -{ - public IEnumerable Formats(); - - public IDictionary DetermineExporters(IEnumerable formats); -} \ No newline at end of file diff --git a/Epsilon.Abstractions/Http/HttpService.cs b/Epsilon.Abstractions/Http/HttpService.cs deleted file mode 100644 index c5643701..00000000 --- a/Epsilon.Abstractions/Http/HttpService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Epsilon.Abstractions.Http; - -public abstract class HttpService -{ - protected HttpService(HttpClient client) => Client = client; - - protected HttpClient Client { get; } -} \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/Activity.cs b/Epsilon.Abstractions/Model/Activity.cs index 23e1f571..4b2150ed 100644 --- a/Epsilon.Abstractions/Model/Activity.cs +++ b/Epsilon.Abstractions/Model/Activity.cs @@ -1,3 +1,7 @@ namespace Epsilon.Abstractions.Model; -public record Activity(int Id, string Name, string? Color = null); \ No newline at end of file +public record Activity( + int Id, + string Name, + string? Color = null +); \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/ArchitecturalLayer.cs b/Epsilon.Abstractions/Model/ArchitecturalLayer.cs index 49e6bdd2..fec7c817 100644 --- a/Epsilon.Abstractions/Model/ArchitecturalLayer.cs +++ b/Epsilon.Abstractions/Model/ArchitecturalLayer.cs @@ -1,3 +1,8 @@ namespace Epsilon.Abstractions.Model; -public record ArchitectureLayer(int Id, string Name, string ShortName, string? Color = null); \ No newline at end of file +public record ArchitectureLayer( + int Id, + string Name, + string ShortName, + string? Color = null +); \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/CompetenceProfile.cs b/Epsilon.Abstractions/Model/CompetenceProfile.cs deleted file mode 100644 index 096eb4fb..00000000 --- a/Epsilon.Abstractions/Model/CompetenceProfile.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Epsilon.Canvas.Abstractions.Model; - -namespace Epsilon.Abstractions.Model; - -public record CompetenceProfile( - IHboIDomain HboIDomain, - IEnumerable ProfessionalTaskOutcomes, - IEnumerable ProfessionalSkillOutcomes, - IEnumerable Terms, - IEnumerable DecayingAveragesPerTask, - IEnumerable DecayingAveragesPerSkill -); \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/CourseAssignment.cs b/Epsilon.Abstractions/Model/CourseAssignment.cs index ddf8c14b..6a6bde68 100644 --- a/Epsilon.Abstractions/Model/CourseAssignment.cs +++ b/Epsilon.Abstractions/Model/CourseAssignment.cs @@ -1,9 +1,10 @@ -namespace Epsilon.Abstractions.Model +namespace Epsilon.Abstractions.Model; + +public class CourseAssignment { - public class CourseAssignment - { - public string Name { get; set; } = string.Empty; - public string Score { get; set; } = string.Empty; - public string Url { get; set; } = string.Empty; - } + public string Name { get; set; } = string.Empty; + + public string Score { get; set; } = string.Empty; + + public Uri? Url { get; set; } } \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/CourseModule.cs b/Epsilon.Abstractions/Model/CourseModule.cs deleted file mode 100644 index 3fe964b6..00000000 --- a/Epsilon.Abstractions/Model/CourseModule.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Epsilon.Abstractions.Model -{ - public class CourseModule - { - public string Name { get; set; } = string.Empty; - public IEnumerable Outcomes { get; set; } = Enumerable.Empty(); - public string DecayingAverage { get; set; } - } -} \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/CourseModulePackage.cs b/Epsilon.Abstractions/Model/CourseModulePackage.cs new file mode 100644 index 00000000..cfe75a0f --- /dev/null +++ b/Epsilon.Abstractions/Model/CourseModulePackage.cs @@ -0,0 +1,8 @@ +namespace Epsilon.Abstractions.Model; + +public class CourseModulePackage +{ + public string Name { get; set; } = string.Empty; + + public IEnumerable Outcomes { get; set; } = Enumerable.Empty(); +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/CourseOutcome.cs b/Epsilon.Abstractions/Model/CourseOutcome.cs index b06e38c5..185a348f 100644 --- a/Epsilon.Abstractions/Model/CourseOutcome.cs +++ b/Epsilon.Abstractions/Model/CourseOutcome.cs @@ -1,9 +1,10 @@ -namespace Epsilon.Abstractions.Model +namespace Epsilon.Abstractions.Model; + +public class CourseOutcome { - public class CourseOutcome - { - public string Name { get; set; } = string.Empty; - public IEnumerable Assignments { get; set; } = Enumerable.Empty(); - public string Description { get; set; } = string.Empty; - } + public string Name { get; set; } = string.Empty; + + public IEnumerable Assignments { get; set; } = Enumerable.Empty(); + + public string Description { get; set; } = string.Empty; } \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/CoursePage.cs b/Epsilon.Abstractions/Model/CoursePage.cs index 50ab8728..8b5bae08 100644 --- a/Epsilon.Abstractions/Model/CoursePage.cs +++ b/Epsilon.Abstractions/Model/CoursePage.cs @@ -2,19 +2,33 @@ namespace Epsilon.Abstractions.Model; public class CoursePage { - public string Title { get; set; } - public string CreatedAt { get; set; } - public string Url { get; set; } - public string EditingRoles { get; set; } - public string PageId { get; set; } - public string LastEditedBy { get; set; } - public string Published { get; set; } - public string HideFromStudents { get; set; } - public string FrontPage { get; set; } - public string HtmlUrl { get; set; } - public string TodoDate { get; set; } - public string PublishedAt { get; set; } - public string UpdatedAt { get; set; } - public string LockedForUser { get; set; } - public string Body { get; set; } + public string? Title { get; set; } + + public string? CreatedAt { get; set; } + + public Uri? Url { get; set; } + + public string? EditingRoles { get; set; } + + public string? PageId { get; set; } + + public string? LastEditedBy { get; set; } + + public string? Published { get; set; } + + public string? HideFromStudents { get; set; } + + public string? FrontPage { get; set; } + + public Uri? HtmlUrl { get; set; } + + public string? TodoDate { get; set; } + + public string? PublishedAt { get; set; } + + public string? UpdatedAt { get; set; } + + public string? LockedForUser { get; set; } + + public string? Body { get; set; } } \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/ExportData.cs b/Epsilon.Abstractions/Model/ExportData.cs deleted file mode 100644 index fe1b7493..00000000 --- a/Epsilon.Abstractions/Model/ExportData.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Epsilon.Abstractions.Model -{ - public class ExportData - { - public string PersonaHtml { get; set; } = string.Empty; - public IEnumerable CourseModules { get; set; } = Enumerable.Empty(); - } -} \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/HboIDomain2018.cs b/Epsilon.Abstractions/Model/HboIDomain2018.cs index 32155f14..7f6d6a50 100644 --- a/Epsilon.Abstractions/Model/HboIDomain2018.cs +++ b/Epsilon.Abstractions/Model/HboIDomain2018.cs @@ -2,27 +2,27 @@ namespace Epsilon.Abstractions.Model; public class HboIDomain2018 : IHboIDomain { - public static readonly ArchitectureLayer UserInteraction = new(0, "User Interaction", "U", "#E29C53"); - public static readonly ArchitectureLayer OrganisationalProcesses = new(1, "Organisational Processes", "O", "#D16557"); - public static readonly ArchitectureLayer Software = new(2, "Software", "S", "#96B9C0"); - public static readonly ArchitectureLayer HardwareInterfacing = new(3, "Hardware Interfacing", "H", "#8D9292"); - public static readonly ArchitectureLayer Infrastructure = new(4, "Infrastructure", "I", "#6EA7D4"); - - public static readonly Activity ManageAndControl = new(0, "Manage & Control"); - public static readonly Activity Analysis = new(1, "Analysis"); - public static readonly Activity Advise = new(2, "Advise"); - public static readonly Activity Design = new(3, "Design"); - public static readonly Activity Realisation = new(4, "Realisation"); - - public static readonly MasteryLevel LevelOne = new(0, 1, "#8EAADB"); - public static readonly MasteryLevel LevelTwo = new(1, 2, "#A8D08D"); - public static readonly MasteryLevel LevelThree = new(2, 3, "#FFD965"); - public static readonly MasteryLevel LevelFour = new(3, 4, "#B15EB2"); - - public static readonly ProfessionalSkill FutureOrientedOrganisation = new(0, "Future-Oriented Organisation", "FOO"); - public static readonly ProfessionalSkill InvestigativeProblemSolving = new(1, "Investigative Problem Solving", "IPS"); - public static readonly ProfessionalSkill PersonalLeadership = new(2, "Personal Leadership", "PL"); - public static readonly ProfessionalSkill TargetedInteraction = new(3, "Targeted Interaction", "TI"); + public static readonly ArchitectureLayer UserInteraction = new ArchitectureLayer(0, "User Interaction", "U", "#E29C53"); + public static readonly ArchitectureLayer OrganisationalProcesses = new ArchitectureLayer(1, "Organisational Processes", "O", "#D16557"); + public static readonly ArchitectureLayer Software = new ArchitectureLayer(2, "Software", "S", "#96B9C0"); + public static readonly ArchitectureLayer HardwareInterfacing = new ArchitectureLayer(3, "Hardware Interfacing", "H", "#8D9292"); + public static readonly ArchitectureLayer Infrastructure = new ArchitectureLayer(4, "Infrastructure", "I", "#6EA7D4"); + + public static readonly Activity ManageAndControl = new Activity(0, "Manage & Control"); + public static readonly Activity Analysis = new Activity(1, "Analysis"); + public static readonly Activity Advise = new Activity(2, "Advise"); + public static readonly Activity Design = new Activity(3, "Design"); + public static readonly Activity Realisation = new Activity(4, "Realisation"); + + public static readonly MasteryLevel LevelOne = new MasteryLevel(0, 1, "#8EAADB"); + public static readonly MasteryLevel LevelTwo = new MasteryLevel(1, 2, "#A8D08D"); + public static readonly MasteryLevel LevelThree = new MasteryLevel(2, 3, "#FFD965"); + public static readonly MasteryLevel LevelFour = new MasteryLevel(3, 4, "#B15EB2"); + + public static readonly ProfessionalSkill FutureOrientedOrganisation = new ProfessionalSkill(0, "Future-Oriented Organisation", "FOO"); + public static readonly ProfessionalSkill InvestigativeProblemSolving = new ProfessionalSkill(1, "Investigative Problem Solving", "IPS"); + public static readonly ProfessionalSkill PersonalLeadership = new ProfessionalSkill(2, "Personal Leadership", "PL"); + public static readonly ProfessionalSkill TargetedInteraction = new ProfessionalSkill(3, "Targeted Interaction", "TI"); public IEnumerable ArchitectureLayers => new[] { diff --git a/Epsilon.Abstractions/Service/ICompetenceComponentService.cs b/Epsilon.Abstractions/Service/ICompetenceComponentService.cs new file mode 100644 index 00000000..9a8fb590 --- /dev/null +++ b/Epsilon.Abstractions/Service/ICompetenceComponentService.cs @@ -0,0 +1,14 @@ +using Epsilon.Abstractions.Component; + +namespace Epsilon.Abstractions.Service; + +public interface ICompetenceComponentService +{ + IAsyncEnumerable GetComponents(DateTime startDate, DateTime endDate); + + IAsyncEnumerable GetComponents(DateTime startDate, DateTime endDate) where TComponent : ICompetenceComponent; + + Task GetComponent(string name, DateTime startDate, DateTime endDate); + + Task GetComponent(string name, DateTime startDate, DateTime endDate) where TComponent : class, ICompetenceComponent; +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Service/ICompetenceDocumentService.cs b/Epsilon.Abstractions/Service/ICompetenceDocumentService.cs new file mode 100644 index 00000000..d2bee6e3 --- /dev/null +++ b/Epsilon.Abstractions/Service/ICompetenceDocumentService.cs @@ -0,0 +1,6 @@ +namespace Epsilon.Abstractions.Service; + +public interface ICompetenceDocumentService +{ + Task WriteDocument(Stream stream, DateTime startDate, DateTime endDate); +} \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Epsilon.Canvas.Abstractions.csproj b/Epsilon.Canvas.Abstractions/Epsilon.Canvas.Abstractions.csproj index eb2460e9..d3f87514 100644 --- a/Epsilon.Canvas.Abstractions/Epsilon.Canvas.Abstractions.csproj +++ b/Epsilon.Canvas.Abstractions/Epsilon.Canvas.Abstractions.csproj @@ -4,6 +4,16 @@ net6.0 enable enable + latest-All + true + true + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/Epsilon.Canvas.Abstractions/ICanvasModuleCollectionFetcher.cs b/Epsilon.Canvas.Abstractions/ICanvasModuleCollectionFetcher.cs deleted file mode 100644 index f10fa543..00000000 --- a/Epsilon.Canvas.Abstractions/ICanvasModuleCollectionFetcher.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Epsilon.Canvas.Abstractions.Model; - -namespace Epsilon.Canvas.Abstractions; - -public interface ICanvasModuleCollectionFetcher -{ - public IAsyncEnumerable GetAll(int courseId, IEnumerable? allowedModules); -} \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/Module.cs b/Epsilon.Canvas.Abstractions/Model/CourseModule.cs similarity index 92% rename from Epsilon.Canvas.Abstractions/Model/Module.cs rename to Epsilon.Canvas.Abstractions/Model/CourseModule.cs index 901962b5..74e1e959 100644 --- a/Epsilon.Canvas.Abstractions/Model/Module.cs +++ b/Epsilon.Canvas.Abstractions/Model/CourseModule.cs @@ -2,7 +2,7 @@ namespace Epsilon.Canvas.Abstractions.Model; -public record Module( +public record CourseModule( [property: JsonPropertyName("id")] int Id, [property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("items_count")] int Count, diff --git a/Epsilon.Canvas.Abstractions/Model/EnrollmentTermCollection.cs b/Epsilon.Canvas.Abstractions/Model/EnrollmentTermCollection.cs index c53eb04e..184a382f 100644 --- a/Epsilon.Canvas.Abstractions/Model/EnrollmentTermCollection.cs +++ b/Epsilon.Canvas.Abstractions/Model/EnrollmentTermCollection.cs @@ -1,5 +1,4 @@ -using System.Collections; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Epsilon.Canvas.Abstractions.Model; diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs index 5354ed79..8d570e6a 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs @@ -3,6 +3,9 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl; public record AssessmentRating( - [property: JsonPropertyName("criterion")] Criterion Criterion, + [property: JsonPropertyName("criterion")] Criterion? Criterion, [property: JsonPropertyName("points")] double? Points -); \ No newline at end of file +) +{ + public bool IsMastery => Points >= Criterion?.MasteryPoints; +} \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/Assignment.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/Assignment.cs index af584ad5..ebc20383 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/Assignment.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/Assignment.cs @@ -4,5 +4,6 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl; public record Assignment( [property: JsonPropertyName("name")] string? Name, - [property: JsonPropertyName("modules")] List? Modules + [property: JsonPropertyName("modules")] IEnumerable? Modules , + [property: JsonPropertyName("rubric")] Rubric? Rubric ); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/CanvasGraphQlQueryResponse.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/CanvasGraphQlQueryResponse.cs new file mode 100644 index 00000000..f0318dac --- /dev/null +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/CanvasGraphQlQueryResponse.cs @@ -0,0 +1,7 @@ +using System.Text.Json.Serialization; + +namespace Epsilon.Canvas.Abstractions.Model.GraphQl; + +public record CanvasGraphQlQueryResponse( + [property: JsonPropertyName("data")] CanvasGraphQlSchema? Data +); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/CanvasGraphQlSchema.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/CanvasGraphQlSchema.cs new file mode 100644 index 00000000..02d4e9fc --- /dev/null +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/CanvasGraphQlSchema.cs @@ -0,0 +1,8 @@ +using System.Text.Json.Serialization; + +namespace Epsilon.Canvas.Abstractions.Model.GraphQl; + +public record CanvasGraphQlSchema( + [property: JsonPropertyName("allCourses")] IEnumerable? Courses, + [property: JsonPropertyName("course")] Course? Course +); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/Criteria.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/Criteria.cs new file mode 100644 index 00000000..728d7082 --- /dev/null +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/Criteria.cs @@ -0,0 +1,7 @@ +using System.Text.Json.Serialization; + +namespace Epsilon.Canvas.Abstractions.Model.GraphQl; + +public record Criteria( + [property: JsonPropertyName("outcome")] Outcome? Outcome +); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/Criterion.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/Criterion.cs index a8e23006..a8f289a5 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/Criterion.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/Criterion.cs @@ -4,5 +4,5 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl; public record Criterion( [property: JsonPropertyName("masteryPoints")] double? MasteryPoints, - [property: JsonPropertyName("outcome")] Outcome Outcome + [property: JsonPropertyName("outcome")] Outcome? Outcome ); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/Outcome.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/Outcome.cs index d9caddc1..6bdbd704 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/Outcome.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/Outcome.cs @@ -3,5 +3,6 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl; public record Outcome( - [property: JsonPropertyName("_id")] int Id + [property: JsonPropertyName("_id")] int Id, + [property: JsonPropertyName("title")] string Title ); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/Rubric.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/Rubric.cs new file mode 100644 index 00000000..c763d62b --- /dev/null +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/Rubric.cs @@ -0,0 +1,7 @@ +using System.Text.Json.Serialization; + +namespace Epsilon.Canvas.Abstractions.Model.GraphQl; + +public record Rubric( + [property: JsonPropertyName("criteria")] IEnumerable? Criteria +); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/RubricAssessmentNode.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/RubricAssessmentNode.cs index 901f2b2a..e2f0ef08 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/RubricAssessmentNode.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/RubricAssessmentNode.cs @@ -3,5 +3,5 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl; public record RubricAssessmentNode( - [property: JsonPropertyName("assessmentRatings")] IReadOnlyList AssessmentRatings + [property: JsonPropertyName("assessmentRatings")] IReadOnlyList? AssessmentRatings ); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/RubricAssessmentsConnection.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/RubricAssessmentsConnection.cs index a68992fd..0c01078e 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/RubricAssessmentsConnection.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/RubricAssessmentsConnection.cs @@ -3,5 +3,5 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl; public record RubricAssessmentsConnection( - [property: JsonPropertyName("nodes")] List? Nodes + [property: JsonPropertyName("nodes")] IEnumerable? Nodes ); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsConnectionNode.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsConnectionNode.cs index b5aded9c..013fdbb2 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsConnectionNode.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsConnectionNode.cs @@ -4,5 +4,7 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl; public record SubmissionsConnectionNode( [property: JsonPropertyName("postedAt")] DateTime? PostedAt, - [property: JsonPropertyName("submissionHistoriesConnection")] SubmissionsHistoriesConnection SubmissionsHistories + [property: JsonPropertyName("assignment")] Assignment? Assignment, + [property: JsonPropertyName("submissionHistoriesConnection")] SubmissionsHistoriesConnection SubmissionsHistories, + [property: JsonPropertyName("rubricAssessmentsConnection")] RubricAssessmentsConnection RubricAssessmentsConnection ); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsHistoriesConnectionNode.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsHistoriesConnectionNode.cs index 7c6ed535..bf875031 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsHistoriesConnectionNode.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/SubmissionsHistoriesConnectionNode.cs @@ -4,5 +4,7 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl; public record SubmissionsHistoriesConnectionNode( [property: JsonPropertyName("attempt")] int? Attempt, + [property: JsonPropertyName("submittedAt")] DateTime? SubmittedAt, + [property: JsonPropertyName("assignment")] Assignment? Assignment, [property: JsonPropertyName("rubricAssessmentsConnection")] RubricAssessmentsConnection? RubricAssessments ); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/ModuleOutcomeResultCollection.cs b/Epsilon.Canvas.Abstractions/Model/ModuleOutcomeResultCollection.cs index 0ed006f7..f1ce09e6 100644 --- a/Epsilon.Canvas.Abstractions/Model/ModuleOutcomeResultCollection.cs +++ b/Epsilon.Canvas.Abstractions/Model/ModuleOutcomeResultCollection.cs @@ -1,3 +1,3 @@ namespace Epsilon.Canvas.Abstractions.Model; -public record ModuleOutcomeResultCollection(Module Module, OutcomeResultCollection Collection); \ No newline at end of file +public record ModuleOutcomeResultCollection(CourseModule CourseModule, OutcomeResultCollection Collection); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/Outcome.cs b/Epsilon.Canvas.Abstractions/Model/Outcome.cs index 9f60e510..f650d7ba 100644 --- a/Epsilon.Canvas.Abstractions/Model/Outcome.cs +++ b/Epsilon.Canvas.Abstractions/Model/Outcome.cs @@ -11,12 +11,13 @@ public record Outcome( { public string ShortDescription() { - string description = RemoveHtml(); - //Function gives only the short English description back of the outcome. + var description = RemoveHtml(); + + // Function gives only the short English description back of the outcome. var startPos = description.IndexOf(" EN ", StringComparison.Ordinal) + " EN ".Length; var endPos = description.IndexOf(" NL ", StringComparison.Ordinal); - return description.Substring(startPos, endPos - startPos); + return description[startPos..endPos]; } private string RemoveHtml() diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeGradeStatus.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeGradeStatus.cs new file mode 100644 index 00000000..37c0b7aa --- /dev/null +++ b/Epsilon.Canvas.Abstractions/Model/OutcomeGradeStatus.cs @@ -0,0 +1,9 @@ +namespace Epsilon.Canvas.Abstractions.Model; + +public enum OutcomeGradeStatus +{ + Mastered, + NotMastered, + NotGraded, + NotAssessed, +} \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs index 09e169c2..9cbf3ac6 100644 --- a/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs +++ b/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs @@ -6,20 +6,4 @@ public record OutcomeResultCollection( [property: JsonPropertyName("outcome_results")] IEnumerable OutcomeResults, [property: JsonPropertyName("linked")] OutcomeResultCollectionLink? Links -) -{ - public double GetDecayingAverage() - { - var decayingAverage = 0.0; - - foreach(var grade in OutcomeResults) - { - if (grade.Score != null) - { - decayingAverage = decayingAverage * 0.35 + grade.Score.Value * 0.65; - } - } - - return decayingAverage; - } -} \ No newline at end of file +); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollectionLink.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollectionLink.cs index 9c5e1a6e..42c762b7 100644 --- a/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollectionLink.cs +++ b/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollectionLink.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System.Globalization; +using System.Text.Json.Serialization; namespace Epsilon.Canvas.Abstractions.Model; @@ -8,8 +9,8 @@ public record OutcomeResultCollectionLink( ) { public IDictionary OutcomesDictionary => Outcomes.DistinctBy(static o => o.Id) - .ToDictionary(static o => o.Id.ToString(), static o => o); - + .ToDictionary(static o => o.Id.ToString(CultureInfo.InvariantCulture), static o => o); + public IDictionary AlignmentsDictionary => Alignments.DistinctBy(static a => a.Id) .ToDictionary(static a => a.Id, static a => a); } \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeResultLink.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeResultLink.cs index d11b5a5e..7af6ed98 100644 --- a/Epsilon.Canvas.Abstractions/Model/OutcomeResultLink.cs +++ b/Epsilon.Canvas.Abstractions/Model/OutcomeResultLink.cs @@ -6,4 +6,5 @@ public record OutcomeResultLink( [property: JsonPropertyName("user")] string? User, [property: JsonPropertyName("learning_outcome")] string? Outcome, [property: JsonPropertyName("alignment")] string? Alignment, - [property: JsonPropertyName("assignment")] string? Assignment); \ No newline at end of file + [property: JsonPropertyName("assignment")] string? Assignment +); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/Page.cs b/Epsilon.Canvas.Abstractions/Model/Page.cs index 2a82460d..9cb58604 100644 --- a/Epsilon.Canvas.Abstractions/Model/Page.cs +++ b/Epsilon.Canvas.Abstractions/Model/Page.cs @@ -5,13 +5,13 @@ namespace Epsilon.Canvas.Abstractions.Model; public record Page( [property: JsonPropertyName("title")] string? Title, [property: JsonPropertyName("created_at")] string? CreatedAt, - [property: JsonPropertyName("url")] string? Url, + [property: JsonPropertyName("url")] Uri? Url, [property: JsonPropertyName("editing_roles")] string? EditingRoles, [property: JsonPropertyName("page_id")] int? PageId, [property: JsonPropertyName("published")] bool? Published, [property: JsonPropertyName("hide_from_students")] bool? HideFromStudents, [property: JsonPropertyName("front_page")] bool? FrontPage, - [property: JsonPropertyName("html_url")] string? HTMLUrl, + [property: JsonPropertyName("html_url")] Uri? HtmlUrl, [property: JsonPropertyName("todo_date")] DateTime? TodoDate, [property: JsonPropertyName("publish_at")] DateTime? PublishAt, [property: JsonPropertyName("updated_at")] DateTime? UpdatedAt, diff --git a/Epsilon.Canvas.Abstractions/QueryResponse/GetAllUserCoursesSubmissionOutcomes.cs b/Epsilon.Canvas.Abstractions/QueryResponse/GetAllUserCoursesSubmissionOutcomes.cs deleted file mode 100644 index 35b61f2b..00000000 --- a/Epsilon.Canvas.Abstractions/QueryResponse/GetAllUserCoursesSubmissionOutcomes.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; -using Epsilon.Canvas.Abstractions.Model.GraphQl; - -namespace Epsilon.Canvas.Abstractions.QueryResponse; - -public record GetAllUserCoursesSubmissionOutcomes( - [property: JsonPropertyName("data")] GetAllUserCoursesSubmissionOutcomes.CourseData? Data -) -{ - public record CourseData( - [property: JsonPropertyName("allCourses")] List? Courses - ); -}; \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/QueryResponse/GetUserCourseSubmissionOutcomes.cs b/Epsilon.Canvas.Abstractions/QueryResponse/GetUserCourseSubmissionOutcomes.cs deleted file mode 100644 index 785035d7..00000000 --- a/Epsilon.Canvas.Abstractions/QueryResponse/GetUserCourseSubmissionOutcomes.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; -using Epsilon.Canvas.Abstractions.Model.GraphQl; - -namespace Epsilon.Canvas.Abstractions.QueryResponse; - -public record GetUserCourseSubmissionOutcomes( - [property: JsonPropertyName("data")] GetUserCourseSubmissionOutcomes.CourseData? Data -) -{ - public record CourseData( - [property: JsonPropertyName("course")] Course? Course - ); -}; \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Service/IAccountHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IAccountHttpService.cs index 5f6f4020..110c880e 100644 --- a/Epsilon.Canvas.Abstractions/Service/IAccountHttpService.cs +++ b/Epsilon.Canvas.Abstractions/Service/IAccountHttpService.cs @@ -4,5 +4,5 @@ namespace Epsilon.Canvas.Abstractions.Service; public interface IAccountHttpService { - public Task>? GetAllTerms(int accountId); + public Task?> GetAllTerms(int accountId); } \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Service/IAssignmentHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IAssignmentHttpService.cs index 902a6299..4c4e0409 100644 --- a/Epsilon.Canvas.Abstractions/Service/IAssignmentHttpService.cs +++ b/Epsilon.Canvas.Abstractions/Service/IAssignmentHttpService.cs @@ -5,5 +5,6 @@ namespace Epsilon.Canvas.Abstractions.Service; public interface IAssignmentHttpService { Task?> GetAll(int courseId, IEnumerable include); + Task GetById(int courseId, int id); } \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs index 8afbe732..b36d6ab5 100644 --- a/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs +++ b/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs @@ -2,5 +2,5 @@ namespace Epsilon.Canvas.Abstractions.Service; public interface IFileHttpService { - Task?> GetFileByteArray(string url); + Task?> GetFileByteArray(Uri url); } \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Service/IModuleHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IModuleHttpService.cs index 4ba1331f..6a131d2e 100644 --- a/Epsilon.Canvas.Abstractions/Service/IModuleHttpService.cs +++ b/Epsilon.Canvas.Abstractions/Service/IModuleHttpService.cs @@ -4,7 +4,9 @@ namespace Epsilon.Canvas.Abstractions.Service; public interface IModuleHttpService { - Task?> GetAll(int courseId, IEnumerable include); - Task GetById(int courseId, int id); + Task?> GetAll(int courseId, IEnumerable include); + + Task GetById(int courseId, int id); + Task?> GetAllItems(int courseId, int moduleId, int limit = 100); } \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Service/IOutcomeHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IOutcomeHttpService.cs index dec04af8..5fc7f154 100644 --- a/Epsilon.Canvas.Abstractions/Service/IOutcomeHttpService.cs +++ b/Epsilon.Canvas.Abstractions/Service/IOutcomeHttpService.cs @@ -5,5 +5,6 @@ namespace Epsilon.Canvas.Abstractions.Service; public interface IOutcomeHttpService { Task Find(int id); + Task GetResults(int courseId, IEnumerable include); } \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs index a9390d6c..5ec0ea50 100644 --- a/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs +++ b/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs @@ -4,7 +4,7 @@ namespace Epsilon.Canvas.Abstractions.Service; public interface IPageHttpService { - Task GetPageByName(int courseId, string pageName); + Task GetPageByName(int courseId, string pageName); Task?> GetAll(int courseId, IEnumerable include); } \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Service/IPaginatorHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IPaginatorHttpService.cs index 6251bb4d..772ac86b 100644 --- a/Epsilon.Canvas.Abstractions/Service/IPaginatorHttpService.cs +++ b/Epsilon.Canvas.Abstractions/Service/IPaginatorHttpService.cs @@ -2,5 +2,5 @@ public interface IPaginatorHttpService { - public Task> GetAllPages(HttpMethod method, string uri); + public Task> GetAllPages(HttpMethod method, Uri uri); } \ No newline at end of file diff --git a/Epsilon.Canvas.Tests/CanvasModuleCollectionFetcherTests.cs b/Epsilon.Canvas.Tests/CanvasModuleCollectionFetcherTests.cs deleted file mode 100644 index 35f666c5..00000000 --- a/Epsilon.Canvas.Tests/CanvasModuleCollectionFetcherTests.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Epsilon.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Service; -using Microsoft.Extensions.Logging; -using Moq; -using System.Text.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xunit; -using Epsilon.Canvas.Abstractions; - -namespace Epsilon.Canvas.Tests -{ - public class CanvasModuleCollectionFetcherTests - { - [Fact] - public async Task - GivenAllowedModulesAndCourseId_WhenModuleAndOutcomeServiceAreMocked_ThenReturnsExpectedModuleOutcomeResultCollection() - { - // Arrange - var courseId = 123; - var allowedModules = new[] {"Module 1", "Module 3"}; - var outcomeServiceMock = new Mock(); - var moduleServiceMock = new Mock(); - - var expectedResults = new List - { - new ModuleOutcomeResultCollection( - new Module(1, "Module 1", 3, new List - { - new ModuleItem(1, "Module 1 Item 1", ModuleItemType.Page, 1), - new ModuleItem(2, "Module 1 Item 2", ModuleItemType.Assignment, 2), - new ModuleItem(3, "Module 1 Item 3", ModuleItemType.Quiz, 3) - }), - new OutcomeResultCollection( - new List { }, - new OutcomeResultCollectionLink( - new List - { - new Outcome(1, "Outcome 1", "Outcome 1 EN Short Description NL Long Description"), - new Outcome(2, "Outcome 2", "Outcome 2 EN Short Description NL Long Description") - }, - new List { } - ) - ) - ), - new ModuleOutcomeResultCollection( - new Module(3, "Module 3", 2, new List - { - new ModuleItem(4, "Module 3 Item 1", ModuleItemType.Assignment, 4), - new ModuleItem(5, "Module 3 Item 2", ModuleItemType.Assignment, 5) - }), - new OutcomeResultCollection( - new List { }, - new OutcomeResultCollectionLink( - new List - { - new Outcome(1, "Outcome 1", "Outcome 1 EN Short Description NL Long Description"), - new Outcome(2, "Outcome 2", "Outcome 2 EN Short Description NL Long Description") - }, - new List { } - ) - ) - ) - }; - - outcomeServiceMock - .Setup(s => s.GetResults(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OutcomeResultCollection( - new List - { - new OutcomeResult(false, 3, new OutcomeResultLink("user1", "1", "1", "2")), - new OutcomeResult(true, 4.5, new OutcomeResultLink("user2", "2", "2", "3")), - new OutcomeResult(false, null, new OutcomeResultLink("user1", "1", "1", "3")), - }, - new OutcomeResultCollectionLink( - new List - { - new Outcome(1, "Outcome 1", "Outcome 1 EN Short Description NL Long Description"), - new Outcome(2, "Outcome 2", "Outcome 2 EN Short Description NL Long Description"), - }, - new List - { - new Alignment("1", "Alignment 1", new Uri("https://alignment1.com")), - new Alignment("2", "Alignment 2", new Uri("https://alignment2.com")), - new Alignment("3", "Alignment 3", new Uri("https://alignment3.com")), - } - ) - )); - - moduleServiceMock - .Setup(s => s.GetAll(It.IsAny(), It.IsAny())) - .ReturnsAsync(new List - { - new Module(1, "Module 1", 3, new List - { - new ModuleItem(1, "Module 1 Item 1", ModuleItemType.Page, 1), - new ModuleItem(2, "Module 1 Item 2", ModuleItemType.Assignment, 2), - new ModuleItem(3, "Module 1 Item 3", ModuleItemType.Quiz, 3) - }), - new Module(2, "Module 2", 0, new List()), - new Module(3, "Module 3", 2, new List - { - new ModuleItem(4, "Module 3 Item 1", ModuleItemType.Assignment, 4), - new ModuleItem(5, "Module 3 Item 2", ModuleItemType.Assignment, 5) - }), - }); - - var canvasModuleCollectionFetcher = new CanvasModuleCollectionFetcher( - moduleServiceMock.Object, outcomeServiceMock.Object - ); - - // Act - var result = new List(); - await foreach (var item in canvasModuleCollectionFetcher.GetAll(courseId, allowedModules)) - { - result.Add(item); - } - - // Assert - Assert.Equal(JsonSerializer.Serialize(expectedResults), JsonSerializer.Serialize(result)); - } - } -} \ No newline at end of file diff --git a/Epsilon.Canvas.Tests/Epsilon.Canvas.Tests.csproj b/Epsilon.Canvas.Tests/Epsilon.Canvas.Tests.csproj index 084d2ca5..8fa4588c 100644 --- a/Epsilon.Canvas.Tests/Epsilon.Canvas.Tests.csproj +++ b/Epsilon.Canvas.Tests/Epsilon.Canvas.Tests.csproj @@ -3,15 +3,21 @@ net6.0 enable - false + latest-All + true + true - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -19,7 +25,7 @@ - + diff --git a/Epsilon.Canvas.Tests/Services/AssignmentHttpServiceTests.cs b/Epsilon.Canvas.Tests/Services/AssignmentHttpServiceTests.cs index f66972a6..c23cda0c 100644 --- a/Epsilon.Canvas.Tests/Services/AssignmentHttpServiceTests.cs +++ b/Epsilon.Canvas.Tests/Services/AssignmentHttpServiceTests.cs @@ -1,55 +1,52 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; -using System.Net; using System.Net.Http; -using System.Net.Http.Json; -using System.Threading; using System.Threading.Tasks; -using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Service; using Moq; -using Moq.Protected; using Xunit; -namespace Epsilon.Canvas.Service.Tests +namespace Epsilon.Canvas.Tests.Services; + +public class AssignmentHttpServiceTests { - public class AssignmentHttpServiceTests + [Fact] + public async Task GivenCourseIdAndIncludeSubmissionAndRubricAssessment_WhenCanvasAssignmentsAreRetrieved_ThenAssignmentsAreReturned() { - private readonly HttpClient _httpClient; - private readonly Mock _paginatorHttpServiceMock; - private readonly AssignmentHttpService _assignmentHttpService; - - public AssignmentHttpServiceTests() + // Arrange + const int courseId = 123; + var include = new[] { - _httpClient = new HttpClient(); - _paginatorHttpServiceMock = new Mock(); - _assignmentHttpService = new AssignmentHttpService(_httpClient, _paginatorHttpServiceMock.Object); - } - - [Fact] - public async Task - GivenCourseIdAndIncludeSubmissionAndRubricAssessment_WhenCanvasAssignmentsAreRetrieved_ThenAssignmentsAreReturned() + "submission", + "rubric_assessment", + }; + var assignments = new[] { - // Arrange - var courseId = 123; - var include = new[] {"submission", "rubric_assessment"}; - var assignments = new[] + new Assignment(1, "Assignment 1", new Uri("https://example.com/1"), null), + new Assignment(2, "Assignment 2", new Uri("https://example.com/2"), new Submission(null, null, null, null)), + new Assignment(3, "Assignment 3", new Uri("https://example.com/3"), new Submission(null, null, new RubricAssessment(8.5, 1, Enumerable.Empty()), null) + ), + }; + + var paginatorHttpServiceMock = new Mock(); + paginatorHttpServiceMock.Setup(static x => x.GetAllPages>( + HttpMethod.Get, + new Uri("v1/courses/{courseId}/assignments?include[]=submission&include[]=rubric_assessment"))) + .ReturnsAsync(new[] { - new Assignment(1, "Assignment 1", new("https://example.com/1"), null), - new Assignment(2, "Assignment 2", new("https://example.com/2"), new Submission(null, null, null, null)), - new Assignment(3, "Assignment 3", new("https://example.com/3"), - new Submission(null, null, new RubricAssessment(8.5, 1, Enumerable.Empty()), null)), - }; - _paginatorHttpServiceMock.Setup(x => x.GetAllPages>(HttpMethod.Get, - $"v1/courses/{courseId}/assignments?include[]=submission&include[]=rubric_assessment")) - .ReturnsAsync(new[] {assignments}); + assignments, + }); + + using var httpClientMock = new HttpClient(); + var assignmentHttpService = new AssignmentHttpService(httpClientMock, paginatorHttpServiceMock.Object); - // Act - var result = await _assignmentHttpService.GetAll(courseId, include); + // Act + var result = await assignmentHttpService.GetAll(courseId, include); - // Assert - Assert.Equal(assignments, result); - } + // Assert + Assert.Equal(assignments, result); } } \ No newline at end of file diff --git a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs deleted file mode 100644 index c2af4758..00000000 --- a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Diagnostics; -using Epsilon.Abstractions.Export; -using Epsilon.Canvas.Abstractions; -using Epsilon.Canvas.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Service; - -namespace Epsilon.Canvas; - -public class CanvasModuleCollectionFetcher : ICanvasModuleCollectionFetcher -{ - private readonly IModuleHttpService _moduleService; - private readonly IOutcomeHttpService _outcomeService; - - public CanvasModuleCollectionFetcher( - IModuleHttpService moduleService, - IOutcomeHttpService outcomeService - ) - { - _moduleService = moduleService; - _outcomeService = outcomeService; - } - - public async IAsyncEnumerable GetAll(int courseId, - IEnumerable? allowedModules) - { - var response = await _outcomeService.GetResults(courseId, new[] {"outcomes", "alignments"}); - var modules = await _moduleService.GetAll(courseId, new[] {"items"}); - - Debug.Assert(response != null, nameof(response) + " != null"); - Debug.Assert(modules != null, nameof(modules) + " != null"); - - foreach (var module in modules.ToArray()) - { - if (allowedModules == null || !allowedModules.Any() || allowedModules.Contains(module.Name)) - { - Debug.Assert(module.Items != null, "module.Items != null"); - - var ids = module.Items.Select(static i => $"assignment_{i.ContentId}"); - - Debug.Assert(response.Links?.Alignments != null, "response.Links?.Alignments != null"); - - yield return new ModuleOutcomeResultCollection(module, new OutcomeResultCollection( - response.OutcomeResults.Where(r => ids.Contains(r.Link.Alignment)), - response.Links with {Alignments = response.Links.Alignments.Where(a => ids.Contains(a.Id))} - )); - } - } - } -} \ No newline at end of file diff --git a/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs b/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs index 3b8f0bab..35d7e606 100644 --- a/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs +++ b/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs @@ -1,5 +1,4 @@ using System.Net.Http.Headers; -using Epsilon.Canvas.Abstractions; using Epsilon.Canvas.Abstractions.Converter; using Epsilon.Canvas.Abstractions.Service; using Epsilon.Canvas.Converter; @@ -40,8 +39,6 @@ public static IServiceCollection AddCanvas(this IServiceCollection services, ICo services.AddScoped(); - services.AddScoped(); - return services; } } \ No newline at end of file diff --git a/Epsilon.Canvas/CanvasSettings.cs b/Epsilon.Canvas/CanvasSettings.cs index b46d7ca8..ac8c1029 100644 --- a/Epsilon.Canvas/CanvasSettings.cs +++ b/Epsilon.Canvas/CanvasSettings.cs @@ -5,7 +5,7 @@ namespace Epsilon.Canvas; public record CanvasSettings { [Required] - public Uri ApiUrl { get; set; } = new("https://fhict.instructure.com/api/"); + public Uri ApiUrl { get; set; } = new Uri("https://fhict.instructure.com/api/"); [Required] public int CourseId { get; set; } diff --git a/Epsilon.Canvas/Converter/LinkHeaderConverter.cs b/Epsilon.Canvas/Converter/LinkHeaderConverter.cs index ab318a5c..c67fefca 100644 --- a/Epsilon.Canvas/Converter/LinkHeaderConverter.cs +++ b/Epsilon.Canvas/Converter/LinkHeaderConverter.cs @@ -6,17 +6,14 @@ namespace Epsilon.Canvas.Converter; public class LinkHeaderConverter : ILinkHeaderConverter { - private static readonly Regex s_relationRegex = new("(?<=rel=\").+?(?=\")", RegexOptions.IgnoreCase); - private static readonly Regex s_linkRegex = new("(?<=<).+?(?=>)", RegexOptions.IgnoreCase); + private static readonly Regex s_relationRegex = new Regex("(?<=rel=\").+?(?=\")", RegexOptions.IgnoreCase); + private static readonly Regex s_linkRegex = new Regex("(?<=<).+?(?=>)", RegexOptions.IgnoreCase); public LinkHeader ConvertFrom(HttpResponseMessage response) { - if (!response.Headers.Contains("Link")) - { - throw new KeyNotFoundException("Header does not contain link key"); - } - - return ConvertFrom(response.Headers.GetValues("Link").First()); + return !response.Headers.Contains("Link") + ? throw new KeyNotFoundException("Header does not contain link key") + : ConvertFrom(response.Headers.GetValues("Link").First()); } public LinkHeader ConvertFrom(string from) @@ -31,23 +28,25 @@ public LinkHeader ConvertFrom(string from) if (relMatch.Success && linkMatch.Success) { - var relation = relMatch.Value.ToLower(); + var relation = relMatch.Value.ToUpperInvariant(); var link = linkMatch.Value; switch (relation) { - case "first": + case "FIRST": linkHeader.FirstLink = link; break; - case "prev": + case "PREV": linkHeader.PrevLink = link; break; - case "next": + case "NEXT": linkHeader.NextLink = link; break; - case "last": + case "LAST": linkHeader.LastLink = link; break; + default: + break; } } } diff --git a/Epsilon.Canvas/Epsilon.Canvas.csproj b/Epsilon.Canvas/Epsilon.Canvas.csproj index 69096ea1..1914a099 100644 --- a/Epsilon.Canvas/Epsilon.Canvas.csproj +++ b/Epsilon.Canvas/Epsilon.Canvas.csproj @@ -4,20 +4,26 @@ net6.0 enable enable + latest-All + true + true - - + - - - - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/Epsilon.Canvas/Http/HttpService.cs b/Epsilon.Canvas/Http/HttpService.cs new file mode 100644 index 00000000..07ef9665 --- /dev/null +++ b/Epsilon.Canvas/Http/HttpService.cs @@ -0,0 +1,11 @@ +namespace Epsilon.Canvas.Http; + +public abstract class HttpService +{ + protected HttpService(HttpClient client) + { + Client = client; + } + + protected HttpClient Client { get; } +} \ No newline at end of file diff --git a/Epsilon.Canvas/QueryConstants.cs b/Epsilon.Canvas/QueryConstants.cs index 4481e619..5f282702 100644 --- a/Epsilon.Canvas/QueryConstants.cs +++ b/Epsilon.Canvas/QueryConstants.cs @@ -1,65 +1 @@ -namespace Epsilon.Canvas; - -public static class QueryConstants -{ - public const string GetUserCourseSubmissionOutcomes = @" - query GetUserCourseSubmissionOutcomes { - course(id: $courseId) { - name - submissionsConnection { - nodes { - assignment { - name - modules { - name - } - } - rubricAssessmentsConnection { - nodes { - assessmentRatings { - points - outcome { - title - } - } - user { - name - } - } - } - } - } - } - } - "; - - public const string GetAllUserCoursesSubmissionOutcomes = @" - query MyQuery { - allCourses { - submissionsConnection(studentIds: $studentIds) { - nodes { - submissionHistoriesConnection { - nodes { - rubricAssessmentsConnection { - nodes { - assessmentRatings { - criterion { - outcome { - _id - } - masteryPoints - } - points - } - } - } - attempt - } - } - postedAt - } - } - } - } - "; -} \ No newline at end of file + \ No newline at end of file diff --git a/Epsilon.Canvas/Service/AccountHttpService.cs b/Epsilon.Canvas/Service/AccountHttpService.cs index 6c45cf9d..c568c1bb 100644 --- a/Epsilon.Canvas/Service/AccountHttpService.cs +++ b/Epsilon.Canvas/Service/AccountHttpService.cs @@ -1,20 +1,20 @@ using System.Net.Http.Json; -using System.Text; -using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Http; namespace Epsilon.Canvas.Service; public class AccountHttpService : HttpService, IAccountHttpService { - public AccountHttpService(HttpClient client) : base(client) + public AccountHttpService(HttpClient client) + : base(client) { } - + public async Task?> GetAllTerms(int accountId) { - var request = new HttpRequestMessage(HttpMethod.Get, $"v1/accounts/{accountId}/terms?per_page=100"); + using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/accounts/{accountId}/terms?per_page=100"); var response = await Client.SendAsync(request); var collection = await response.Content.ReadFromJsonAsync(); diff --git a/Epsilon.Canvas/Service/AssignmentHttpService.cs b/Epsilon.Canvas/Service/AssignmentHttpService.cs index bf78e8c1..8468c100 100644 --- a/Epsilon.Canvas/Service/AssignmentHttpService.cs +++ b/Epsilon.Canvas/Service/AssignmentHttpService.cs @@ -1,7 +1,7 @@ using System.Net.Http.Json; -using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Http; namespace Epsilon.Canvas.Service; @@ -9,7 +9,8 @@ public class AssignmentHttpService : HttpService, IAssignmentHttpService { private readonly IPaginatorHttpService _paginator; - public AssignmentHttpService(HttpClient client, IPaginatorHttpService paginator) : base(client) + public AssignmentHttpService(HttpClient client, IPaginatorHttpService paginator) + : base(client) { _paginator = paginator; } @@ -19,13 +20,13 @@ public AssignmentHttpService(HttpClient client, IPaginatorHttpService paginator) var uri = $"v1/courses/{courseId}/assignments"; var query = $"?include[]={string.Join("&include[]=", include)}"; - var pages = await _paginator.GetAllPages>(HttpMethod.Get, uri + query); + var pages = await _paginator.GetAllPages>(HttpMethod.Get, new Uri(uri + query)); return pages.SelectMany(static p => p); } public async Task GetById(int courseId, int id) { - var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/assignments/{id}"); + using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/assignments/{id}"); var response = await Client.SendAsync(request); return await response.Content.ReadFromJsonAsync(); diff --git a/Epsilon.Canvas/Service/FileHttpService.cs b/Epsilon.Canvas/Service/FileHttpService.cs index 121a70cc..5bbbc8e2 100644 --- a/Epsilon.Canvas/Service/FileHttpService.cs +++ b/Epsilon.Canvas/Service/FileHttpService.cs @@ -1,20 +1,16 @@ -using System.Net; -using System.Net.Http.Json; -using Epsilon.Abstractions.Http; -using Epsilon.Canvas.Abstractions.Converter; -using Epsilon.Canvas.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Service; -using HtmlAgilityPack; +using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Http; namespace Epsilon.Canvas.Service; public class FileHttpService : HttpService, IFileHttpService { - public FileHttpService(HttpClient client) : base(client) + public FileHttpService(HttpClient client) + : base(client) { } - public async Task?> GetFileByteArray(string url) + public async Task?> GetFileByteArray(Uri url) { return await Client.GetByteArrayAsync(url); } diff --git a/Epsilon.Canvas/Service/GraphQlHttpService.cs b/Epsilon.Canvas/Service/GraphQlHttpService.cs index 390e360f..1e791318 100644 --- a/Epsilon.Canvas/Service/GraphQlHttpService.cs +++ b/Epsilon.Canvas/Service/GraphQlHttpService.cs @@ -1,18 +1,26 @@ using System.Net.Http.Json; -using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Http; namespace Epsilon.Canvas.Service; public class GraphQlHttpService : HttpService, IGraphQlHttpService { - public GraphQlHttpService(HttpClient client) : base(client) - { } + public GraphQlHttpService(HttpClient client) + : base(client) + { + } public async Task Query(string query) { - var request = new HttpRequestMessage(HttpMethod.Post, "/api/graphql") { - Content = new FormUrlEncodedContent(new Dictionary() {{"query", query}}) + using var request = new HttpRequestMessage(HttpMethod.Post, "/api/graphql") + { + Content = new FormUrlEncodedContent(new Dictionary + { + { + "query", query + }, + }), }; var response = await Client.SendAsync(request); diff --git a/Epsilon.Canvas/Service/ModuleHttpService.cs b/Epsilon.Canvas/Service/ModuleHttpService.cs index f06a4cb5..7253ba77 100644 --- a/Epsilon.Canvas/Service/ModuleHttpService.cs +++ b/Epsilon.Canvas/Service/ModuleHttpService.cs @@ -1,39 +1,40 @@ using System.Net.Http.Json; using System.Text; -using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Http; namespace Epsilon.Canvas.Service; public class ModuleHttpService : HttpService, IModuleHttpService { - public ModuleHttpService(HttpClient client) : base(client) + public ModuleHttpService(HttpClient client) + : base(client) { } - public async Task?> GetAll(int courseId, IEnumerable include) + public async Task?> GetAll(int courseId, IEnumerable include) { var url = new StringBuilder($"v1/courses/{courseId}/modules"); var query = $"?include[]={string.Join("&include[]=", include)}"; - var request = new HttpRequestMessage(HttpMethod.Get, url + query); + using var request = new HttpRequestMessage(HttpMethod.Get, url + query); var response = await Client.SendAsync(request); - return await response.Content.ReadFromJsonAsync>(); + return await response.Content.ReadFromJsonAsync>(); } - public async Task GetById(int courseId, int id) + public async Task GetById(int courseId, int id) { - var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/modules/{id}"); + using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/modules/{id}"); var response = await Client.SendAsync(request); - return await response.Content.ReadFromJsonAsync(); + return await response.Content.ReadFromJsonAsync(); } public async Task?> GetAllItems(int courseId, int moduleId, int limit = 100) { - var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/modules/{moduleId}/items?per_page={limit}"); + using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/modules/{moduleId}/items?per_page={limit}"); var response = await Client.SendAsync(request); return await response.Content.ReadFromJsonAsync>(); diff --git a/Epsilon.Canvas/Service/OutcomeHttpService.cs b/Epsilon.Canvas/Service/OutcomeHttpService.cs index 842412c5..86eef8e5 100644 --- a/Epsilon.Canvas/Service/OutcomeHttpService.cs +++ b/Epsilon.Canvas/Service/OutcomeHttpService.cs @@ -1,8 +1,7 @@ using System.Net.Http.Json; -using System.Text; -using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Http; namespace Epsilon.Canvas.Service; @@ -10,14 +9,15 @@ public class OutcomeHttpService : HttpService, IOutcomeHttpService { private readonly IPaginatorHttpService _paginator; - public OutcomeHttpService(HttpClient client, IPaginatorHttpService paginator) : base(client) + public OutcomeHttpService(HttpClient client, IPaginatorHttpService paginator) + : base(client) { _paginator = paginator; } public async Task Find(int id) { - var request = new HttpRequestMessage(HttpMethod.Get, $"v1/outcomes/{id}"); + using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/outcomes/{id}"); var response = await Client.SendAsync(request); return await response.Content.ReadFromJsonAsync(); @@ -25,20 +25,16 @@ public OutcomeHttpService(HttpClient client, IPaginatorHttpService paginator) : public async Task GetResults(int courseId, IEnumerable include) { - var url = new StringBuilder($"v1/courses/{courseId}/outcome_results"); - var query = $"?include[]={string.Join("&include[]=", include)}"; - - //Console.WriteLine("URL + QUERY " + url + query); + var url = $"v1/courses/{courseId}/outcome_results?include[]={string.Join("&include[]=", include)}"; + var requestUri = new Uri(url, UriKind.Relative); - var responses = await _paginator.GetAllPages(HttpMethod.Get, url + query); + var responses = await _paginator.GetAllPages(HttpMethod.Get, requestUri); var responsesArray = responses.ToArray(); return new OutcomeResultCollection( responsesArray.SelectMany(static r => r.OutcomeResults), new OutcomeResultCollectionLink( responsesArray.SelectMany(static r => r.Links?.Outcomes ?? Array.Empty()), - responsesArray.SelectMany(static r => r.Links?.Alignments ?? Array.Empty()) - ) - ); + responsesArray.SelectMany(static r => r.Links?.Alignments ?? Array.Empty()))); } } \ No newline at end of file diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index 5b2bfd0f..00e2aebe 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -1,14 +1,15 @@ using System.Net; using System.Net.Http.Json; -using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Http; namespace Epsilon.Canvas.Service; public class PageHttpService : HttpService, IPageHttpService { - public PageHttpService(HttpClient client) : base(client) + public PageHttpService(HttpClient client) + : base(client) { } @@ -17,10 +18,9 @@ public PageHttpService(HttpClient client) : base(client) using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); using var response = await Client.SendAsync(request); - if (response.StatusCode == HttpStatusCode.OK) - return (await response.Content.ReadFromJsonAsync()).Body; - - return null; + return response.StatusCode == HttpStatusCode.OK + ? (await response.Content.ReadFromJsonAsync())?.Body + : null; } public async Task?> GetAll(int courseId, IEnumerable include) @@ -28,9 +28,8 @@ public PageHttpService(HttpClient client) : base(client) using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); using var response = await Client.SendAsync(request); - if (response.StatusCode == HttpStatusCode.OK) - return await response.Content.ReadFromJsonAsync>(); - - return null; + return response.StatusCode == HttpStatusCode.OK + ? await response.Content.ReadFromJsonAsync>() + : null; } } \ No newline at end of file diff --git a/Epsilon.Canvas/Service/PaginatorHttpService.cs b/Epsilon.Canvas/Service/PaginatorHttpService.cs index 03c067ed..85082257 100644 --- a/Epsilon.Canvas/Service/PaginatorHttpService.cs +++ b/Epsilon.Canvas/Service/PaginatorHttpService.cs @@ -1,8 +1,8 @@ using System.Net.Http.Json; using System.Web; -using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Converter; using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Http; namespace Epsilon.Canvas.Service; @@ -11,22 +11,26 @@ public class PaginatorHttpService : HttpService, IPaginatorHttpService private const int Limit = 100; private readonly ILinkHeaderConverter _headerConverter; - public PaginatorHttpService(HttpClient client, ILinkHeaderConverter headerConverter) : base(client) + public PaginatorHttpService(HttpClient client, ILinkHeaderConverter headerConverter) + : base(client) { _headerConverter = headerConverter; } - public async Task> GetAllPages(HttpMethod method, string uri) + public async Task> GetAllPages(HttpMethod method, Uri uri) { var pages = new List(); var page = "1"; - uri += !uri.Contains('?') ? "?" : "&"; + var uriString = uri.OriginalString; + uriString += !uriString.Contains('?', StringComparison.InvariantCulture) + ? "?" + : "&"; do { var offset = pages.Count * Limit; - var request = new HttpRequestMessage(method, $"{uri}per_page={Limit}&offset={offset}&page={page}"); + using var request = new HttpRequestMessage(method, $"{uriString}per_page={Limit}&offset={offset}&page={page}"); var response = await Client.SendAsync(request); var value = await response.Content.ReadFromJsonAsync(); @@ -44,7 +48,8 @@ public async Task> GetAllPages(HttpMethod method, var query = HttpUtility.ParseQueryString(new Uri(links.NextLink).Query); page = query["page"]; - } while (pages.Count * Limit % Limit == 0); + } + while (pages.Count * Limit % Limit == 0); return pages; } diff --git a/Epsilon.Canvas/Service/SubmissionHttpService.cs b/Epsilon.Canvas/Service/SubmissionHttpService.cs index 7b301009..39ebf11e 100644 --- a/Epsilon.Canvas/Service/SubmissionHttpService.cs +++ b/Epsilon.Canvas/Service/SubmissionHttpService.cs @@ -1,7 +1,7 @@ using System.Text; -using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Http; namespace Epsilon.Canvas.Service; @@ -9,7 +9,8 @@ public class SubmissionHttpService : HttpService, ISubmissionHttpService { private readonly IPaginatorHttpService _paginator; - public SubmissionHttpService(HttpClient client, IPaginatorHttpService paginator) : base(client) + public SubmissionHttpService(HttpClient client, IPaginatorHttpService paginator) + : base(client) { _paginator = paginator; } @@ -19,7 +20,7 @@ public async Task> GetAllFromStudent(int courseId, IEnum var url = new StringBuilder($"v1/courses/{courseId}/students/submissions"); var query = $"?include[]={string.Join("&include[]=", include)}"; - var responses = await _paginator.GetAllPages>(HttpMethod.Get, url + query); + var responses = await _paginator.GetAllPages>(HttpMethod.Get, new Uri(url + query)); return responses.SelectMany(static r => r); } } \ No newline at end of file diff --git a/Epsilon.Host.Cli/Epsilon.Host.Cli.csproj b/Epsilon.Host.Cli/Epsilon.Host.Cli.csproj deleted file mode 100644 index e6e8536e..00000000 --- a/Epsilon.Host.Cli/Epsilon.Host.Cli.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - Exe - net6.0 - enable - enable - - - - - - - - - - - - - PreserveNewest - - - - - - - - - diff --git a/Epsilon.Host.Cli/Program.cs b/Epsilon.Host.Cli/Program.cs deleted file mode 100644 index 7a9546be..00000000 --- a/Epsilon.Host.Cli/Program.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Epsilon.Host.Cli; -using Epsilon.Extensions; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Serilog; - -IHostBuilder CreateHostBuilder(string[] args) -{ - return Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration(config => - { - config.SetBasePath(Directory.GetCurrentDirectory()); - config.AddJsonFile("appsettings.json"); - config.AddCommandLine(args); - }) - .ConfigureServices(static (context, services) => - { - Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(context.Configuration) - .CreateLogger(); - - services.AddCore(context.Configuration); - services.AddHostedService(); - }); -} - -try -{ - Log.Information("Starting up"); - - await CreateHostBuilder(args) - .UseSerilog() - .Build() - .RunAsync(); -} -catch (Exception ex) -{ - Log.Fatal(ex, "Application was unable to start due to fatal error"); -} -finally -{ - Log.CloseAndFlush(); -} \ No newline at end of file diff --git a/Epsilon.Host.Cli/Startup.cs b/Epsilon.Host.Cli/Startup.cs deleted file mode 100644 index 8cc480bf..00000000 --- a/Epsilon.Host.Cli/Startup.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Epsilon.Abstractions.Export; -using Epsilon.Canvas; -using Epsilon.Canvas.Abstractions; -using Epsilon.Canvas.Abstractions.Service; -using Epsilon.Export; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Epsilon.Host.Cli; - -public class Startup : IHostedService -{ - private readonly ILogger _logger; - private readonly IHostApplicationLifetime _lifetime; - private readonly ExportOptions _exportOptions; - private readonly CanvasSettings _canvasSettings; - private readonly ICanvasModuleCollectionFetcher _collectionFetcher; - private readonly IModuleExporterCollection _exporterCollection; - private readonly IExportDataPackager _exporterDataCollection; - - public Startup( - ILogger logger, - IHostApplicationLifetime lifetime, - IOptions canvasSettings, - IOptions exportSettings, - ICanvasModuleCollectionFetcher collectionFetcher, - IModuleExporterCollection exporterCollection, - IExportDataPackager exporterDataCollection - ) - { - _logger = logger; - _canvasSettings = canvasSettings.Value; - _exportOptions = exportSettings.Value; - _lifetime = lifetime; - _collectionFetcher = collectionFetcher; - _exporterCollection = exporterCollection; - _exporterDataCollection = exporterDataCollection; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - _lifetime.ApplicationStarted.Register(() => Task.Run(ExecuteAsync, cancellationToken)); - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - private async Task ExecuteAsync() - { - try - { - var results = Validate(_canvasSettings).ToArray(); - if (results.Any()) - { - foreach (var validationResult in results) - { - _logger.LogError("Error: {Message}", validationResult.ErrorMessage); - } - - _lifetime.StopApplication(); - return; - } - - var modules = _exportOptions.Modules?.Split(","); - _logger.LogInformation("Targeting Canvas course: {CourseId}, at {Url}", _canvasSettings.CourseId, - _canvasSettings.ApiUrl); - _logger.LogInformation("Downloading results, this may take a few seconds..."); - var items = _collectionFetcher.GetAll(_canvasSettings.CourseId, modules); - var formattedItems = await _exporterDataCollection.GetExportData(items); - - var formats = _exportOptions.Formats.Split(","); - var exporters = _exporterCollection.DetermineExporters(formats).ToArray(); - - _logger.LogInformation("Attempting to use following formats: {Formats}", string.Join(", ", formats)); - - foreach (var (format, exporter) in exporters) - { - _logger.LogInformation("Exporting to {Format} using {Exporter}...", format, exporter.GetType().Name); - - var stream = await exporter.Export(formattedItems, format); - - await using var fileStream = - new FileStream($"{_exportOptions.FormattedOutputName}.{exporter.FileExtension}", FileMode.Create, - FileAccess.Write); - - stream.Position = 0; // Reset position to zero to prepare for copy - await stream.CopyToAsync(fileStream); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error occured:"); - } - finally - { - _lifetime.StopApplication(); - } - } - - private static IEnumerable Validate(object model) - { - var results = new List(); - var context = new ValidationContext(model); - - Validator.TryValidateObject(model, context, results, true); - - return results; - } -} \ No newline at end of file diff --git a/Epsilon.Host.Frontend/pnpm-lock.yaml b/Epsilon.Host.Frontend/pnpm-lock.yaml index e59ead27..7cf285cd 100644 --- a/Epsilon.Host.Frontend/pnpm-lock.yaml +++ b/Epsilon.Host.Frontend/pnpm-lock.yaml @@ -1,57 +1,69 @@ -lockfileVersion: 5.4 - -specifiers: - '@types/node': ^18.15.3 - '@typescript-eslint/eslint-plugin': ^5.55.0 - '@typescript-eslint/parser': ^5.55.0 - '@vitejs/plugin-vue': ^4.0.0 - '@vue/eslint-config-typescript': ^11.0.2 - apexcharts: ^3.37.3 - eslint: ^8.36.0 - eslint-plugin-vue: ^9.9.0 - ts-node: ^10.9.1 - typescript: ^4.9.5 - vite: ^4.1.0 - vue: ^3.2.45 - vue-router: ^4.1.6 - vue3-apexcharts: ^1.4.1 +lockfileVersion: '6.0' dependencies: - apexcharts: 3.37.3 - typescript: 4.9.5 - vue: 3.2.47 - vue-router: 4.1.6_vue@3.2.47 - vue3-apexcharts: 1.4.1_w3ch7w2j635wji4x6c2377ue4i + apexcharts: + specifier: ^3.37.3 + version: 3.37.3 + typescript: + specifier: ^4.9.5 + version: 4.9.5 + vue: + specifier: ^3.2.45 + version: 3.2.47 + vue-router: + specifier: ^4.1.6 + version: 4.1.6(vue@3.2.47) + vue3-apexcharts: + specifier: ^1.4.1 + version: 1.4.1(apexcharts@3.37.3)(vue@3.2.47) devDependencies: - '@types/node': 18.15.3 - '@typescript-eslint/eslint-plugin': 5.55.0_342y7v4tc7ytrrysmit6jo4wri - '@typescript-eslint/parser': 5.55.0_vgl77cfdswitgr47lm5swmv43m - '@vitejs/plugin-vue': 4.0.0_vite@4.1.4+vue@3.2.47 - '@vue/eslint-config-typescript': 11.0.2_75cttubc7yphoaabg6k3yrjnke - eslint: 8.36.0 - eslint-plugin-vue: 9.9.0_eslint@8.36.0 - ts-node: 10.9.1_cbfmry4sbbh4vatmdrsmatfg5a - vite: 4.1.4_@types+node@18.15.3 + '@types/node': + specifier: ^18.15.3 + version: 18.15.3 + '@typescript-eslint/eslint-plugin': + specifier: ^5.55.0 + version: 5.55.0(@typescript-eslint/parser@5.55.0)(eslint@8.36.0)(typescript@4.9.5) + '@typescript-eslint/parser': + specifier: ^5.55.0 + version: 5.55.0(eslint@8.36.0)(typescript@4.9.5) + '@vitejs/plugin-vue': + specifier: ^4.0.0 + version: 4.0.0(vite@4.1.4)(vue@3.2.47) + '@vue/eslint-config-typescript': + specifier: ^11.0.2 + version: 11.0.2(eslint-plugin-vue@9.9.0)(eslint@8.36.0)(typescript@4.9.5) + eslint: + specifier: ^8.36.0 + version: 8.36.0 + eslint-plugin-vue: + specifier: ^9.9.0 + version: 9.9.0(eslint@8.36.0) + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@18.15.3)(typescript@4.9.5) + vite: + specifier: ^4.1.0 + version: 4.1.4(@types/node@18.15.3) packages: - /@babel/helper-string-parser/7.19.4: + /@babel/helper-string-parser@7.19.4: resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-identifier/7.19.1: + /@babel/helper-validator-identifier@7.19.1: resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} engines: {node: '>=6.9.0'} - /@babel/parser/7.21.3: + /@babel/parser@7.21.3: resolution: {integrity: sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==} engines: {node: '>=6.0.0'} hasBin: true dependencies: '@babel/types': 7.21.3 - /@babel/types/7.21.3: + /@babel/types@7.21.3: resolution: {integrity: sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==} engines: {node: '>=6.9.0'} dependencies: @@ -59,32 +71,32 @@ packages: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 - /@cspotcode/source-map-support/0.8.1: + /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} dependencies: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@esbuild/android-arm/0.16.17: - resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} + /@esbuild/android-arm64@0.16.17: + resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} engines: {node: '>=12'} - cpu: [arm] + cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@esbuild/android-arm64/0.16.17: - resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} + /@esbuild/android-arm@0.16.17: + resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} engines: {node: '>=12'} - cpu: [arm64] + cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@esbuild/android-x64/0.16.17: + /@esbuild/android-x64@0.16.17: resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} engines: {node: '>=12'} cpu: [x64] @@ -93,7 +105,7 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64/0.16.17: + /@esbuild/darwin-arm64@0.16.17: resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} engines: {node: '>=12'} cpu: [arm64] @@ -102,7 +114,7 @@ packages: dev: true optional: true - /@esbuild/darwin-x64/0.16.17: + /@esbuild/darwin-x64@0.16.17: resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} engines: {node: '>=12'} cpu: [x64] @@ -111,7 +123,7 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64/0.16.17: + /@esbuild/freebsd-arm64@0.16.17: resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} engines: {node: '>=12'} cpu: [arm64] @@ -120,7 +132,7 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64/0.16.17: + /@esbuild/freebsd-x64@0.16.17: resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} engines: {node: '>=12'} cpu: [x64] @@ -129,25 +141,25 @@ packages: dev: true optional: true - /@esbuild/linux-arm/0.16.17: - resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} + /@esbuild/linux-arm64@0.16.17: + resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} engines: {node: '>=12'} - cpu: [arm] + cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-arm64/0.16.17: - resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} + /@esbuild/linux-arm@0.16.17: + resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} engines: {node: '>=12'} - cpu: [arm64] + cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-ia32/0.16.17: + /@esbuild/linux-ia32@0.16.17: resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} engines: {node: '>=12'} cpu: [ia32] @@ -156,7 +168,7 @@ packages: dev: true optional: true - /@esbuild/linux-loong64/0.16.17: + /@esbuild/linux-loong64@0.16.17: resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} engines: {node: '>=12'} cpu: [loong64] @@ -165,7 +177,7 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el/0.16.17: + /@esbuild/linux-mips64el@0.16.17: resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} engines: {node: '>=12'} cpu: [mips64el] @@ -174,7 +186,7 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64/0.16.17: + /@esbuild/linux-ppc64@0.16.17: resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} engines: {node: '>=12'} cpu: [ppc64] @@ -183,7 +195,7 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64/0.16.17: + /@esbuild/linux-riscv64@0.16.17: resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} engines: {node: '>=12'} cpu: [riscv64] @@ -192,7 +204,7 @@ packages: dev: true optional: true - /@esbuild/linux-s390x/0.16.17: + /@esbuild/linux-s390x@0.16.17: resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} engines: {node: '>=12'} cpu: [s390x] @@ -201,7 +213,7 @@ packages: dev: true optional: true - /@esbuild/linux-x64/0.16.17: + /@esbuild/linux-x64@0.16.17: resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} engines: {node: '>=12'} cpu: [x64] @@ -210,7 +222,7 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64/0.16.17: + /@esbuild/netbsd-x64@0.16.17: resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} engines: {node: '>=12'} cpu: [x64] @@ -219,7 +231,7 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64/0.16.17: + /@esbuild/openbsd-x64@0.16.17: resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} engines: {node: '>=12'} cpu: [x64] @@ -228,7 +240,7 @@ packages: dev: true optional: true - /@esbuild/sunos-x64/0.16.17: + /@esbuild/sunos-x64@0.16.17: resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} engines: {node: '>=12'} cpu: [x64] @@ -237,7 +249,7 @@ packages: dev: true optional: true - /@esbuild/win32-arm64/0.16.17: + /@esbuild/win32-arm64@0.16.17: resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} engines: {node: '>=12'} cpu: [arm64] @@ -246,7 +258,7 @@ packages: dev: true optional: true - /@esbuild/win32-ia32/0.16.17: + /@esbuild/win32-ia32@0.16.17: resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} engines: {node: '>=12'} cpu: [ia32] @@ -255,7 +267,7 @@ packages: dev: true optional: true - /@esbuild/win32-x64/0.16.17: + /@esbuild/win32-x64@0.16.17: resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} engines: {node: '>=12'} cpu: [x64] @@ -264,7 +276,7 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils/4.2.0_eslint@8.36.0: + /@eslint-community/eslint-utils@4.2.0(eslint@8.36.0): resolution: {integrity: sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -274,12 +286,12 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /@eslint-community/regexpp/4.4.0: + /@eslint-community/regexpp@4.4.0: resolution: {integrity: sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc/2.0.1: + /@eslint/eslintrc@2.0.1: resolution: {integrity: sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: @@ -296,12 +308,12 @@ packages: - supports-color dev: true - /@eslint/js/8.36.0: + /@eslint/js@8.36.0: resolution: {integrity: sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@humanwhocodes/config-array/0.11.8: + /@humanwhocodes/config-array@0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} dependencies: @@ -312,32 +324,32 @@ packages: - supports-color dev: true - /@humanwhocodes/module-importer/1.0.1: + /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} dev: true - /@humanwhocodes/object-schema/1.2.1: + /@humanwhocodes/object-schema@1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true - /@jridgewell/resolve-uri/3.1.0: + /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} dev: true - /@jridgewell/sourcemap-codec/1.4.14: + /@jridgewell/sourcemap-codec@1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} dev: true - /@jridgewell/trace-mapping/0.3.9: + /@jridgewell/trace-mapping@0.3.9: resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} dependencies: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 dev: true - /@nodelib/fs.scandir/2.1.5: + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} dependencies: @@ -345,12 +357,12 @@ packages: run-parallel: 1.2.0 dev: true - /@nodelib/fs.stat/2.0.5: + /@nodelib/fs.stat@2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} dev: true - /@nodelib/fs.walk/1.2.8: + /@nodelib/fs.walk@1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} dependencies: @@ -358,35 +370,35 @@ packages: fastq: 1.15.0 dev: true - /@tsconfig/node10/1.0.9: + /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} dev: true - /@tsconfig/node12/1.0.11: + /@tsconfig/node12@1.0.11: resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} dev: true - /@tsconfig/node14/1.0.3: + /@tsconfig/node14@1.0.3: resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} dev: true - /@tsconfig/node16/1.0.3: + /@tsconfig/node16@1.0.3: resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} dev: true - /@types/json-schema/7.0.11: + /@types/json-schema@7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true - /@types/node/18.15.3: + /@types/node@18.15.3: resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==} dev: true - /@types/semver/7.3.13: + /@types/semver@7.3.13: resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} dev: true - /@typescript-eslint/eslint-plugin/5.55.0_342y7v4tc7ytrrysmit6jo4wri: + /@typescript-eslint/eslint-plugin@5.55.0(@typescript-eslint/parser@5.55.0)(eslint@8.36.0)(typescript@4.9.5): resolution: {integrity: sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -398,23 +410,23 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.4.0 - '@typescript-eslint/parser': 5.55.0_vgl77cfdswitgr47lm5swmv43m + '@typescript-eslint/parser': 5.55.0(eslint@8.36.0)(typescript@4.9.5) '@typescript-eslint/scope-manager': 5.55.0 - '@typescript-eslint/type-utils': 5.55.0_vgl77cfdswitgr47lm5swmv43m - '@typescript-eslint/utils': 5.55.0_vgl77cfdswitgr47lm5swmv43m + '@typescript-eslint/type-utils': 5.55.0(eslint@8.36.0)(typescript@4.9.5) + '@typescript-eslint/utils': 5.55.0(eslint@8.36.0)(typescript@4.9.5) debug: 4.3.4 eslint: 8.36.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 semver: 7.3.8 - tsutils: 3.21.0_typescript@4.9.5 + tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser/5.55.0_vgl77cfdswitgr47lm5swmv43m: + /@typescript-eslint/parser@5.55.0(eslint@8.36.0)(typescript@4.9.5): resolution: {integrity: sha512-ppvmeF7hvdhUUZWSd2EEWfzcFkjJzgNQzVST22nzg958CR+sphy8A6K7LXQZd6V75m1VKjp+J4g/PCEfSCmzhw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -426,7 +438,7 @@ packages: dependencies: '@typescript-eslint/scope-manager': 5.55.0 '@typescript-eslint/types': 5.55.0 - '@typescript-eslint/typescript-estree': 5.55.0_typescript@4.9.5 + '@typescript-eslint/typescript-estree': 5.55.0(typescript@4.9.5) debug: 4.3.4 eslint: 8.36.0 typescript: 4.9.5 @@ -434,7 +446,7 @@ packages: - supports-color dev: true - /@typescript-eslint/scope-manager/5.55.0: + /@typescript-eslint/scope-manager@5.55.0: resolution: {integrity: sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: @@ -442,7 +454,7 @@ packages: '@typescript-eslint/visitor-keys': 5.55.0 dev: true - /@typescript-eslint/type-utils/5.55.0_vgl77cfdswitgr47lm5swmv43m: + /@typescript-eslint/type-utils@5.55.0(eslint@8.36.0)(typescript@4.9.5): resolution: {integrity: sha512-ObqxBgHIXj8rBNm0yh8oORFrICcJuZPZTqtAFh0oZQyr5DnAHZWfyw54RwpEEH+fD8suZaI0YxvWu5tYE/WswA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -452,22 +464,22 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.55.0_typescript@4.9.5 - '@typescript-eslint/utils': 5.55.0_vgl77cfdswitgr47lm5swmv43m + '@typescript-eslint/typescript-estree': 5.55.0(typescript@4.9.5) + '@typescript-eslint/utils': 5.55.0(eslint@8.36.0)(typescript@4.9.5) debug: 4.3.4 eslint: 8.36.0 - tsutils: 3.21.0_typescript@4.9.5 + tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types/5.55.0: + /@typescript-eslint/types@5.55.0: resolution: {integrity: sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree/5.55.0_typescript@4.9.5: + /@typescript-eslint/typescript-estree@5.55.0(typescript@4.9.5): resolution: {integrity: sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -482,24 +494,24 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.8 - tsutils: 3.21.0_typescript@4.9.5 + tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils/5.55.0_vgl77cfdswitgr47lm5swmv43m: + /@typescript-eslint/utils@5.55.0(eslint@8.36.0)(typescript@4.9.5): resolution: {integrity: sha512-FkW+i2pQKcpDC3AY6DU54yl8Lfl14FVGYDgBTyGKB75cCwV3KpkpTMFi9d9j2WAJ4271LR2HeC5SEWF/CZmmfw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.2.0_eslint@8.36.0 + '@eslint-community/eslint-utils': 4.2.0(eslint@8.36.0) '@types/json-schema': 7.0.11 '@types/semver': 7.3.13 '@typescript-eslint/scope-manager': 5.55.0 '@typescript-eslint/types': 5.55.0 - '@typescript-eslint/typescript-estree': 5.55.0_typescript@4.9.5 + '@typescript-eslint/typescript-estree': 5.55.0(typescript@4.9.5) eslint: 8.36.0 eslint-scope: 5.1.1 semver: 7.3.8 @@ -508,7 +520,7 @@ packages: - typescript dev: true - /@typescript-eslint/visitor-keys/5.55.0: + /@typescript-eslint/visitor-keys@5.55.0: resolution: {integrity: sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: @@ -516,18 +528,18 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /@vitejs/plugin-vue/4.0.0_vite@4.1.4+vue@3.2.47: + /@vitejs/plugin-vue@4.0.0(vite@4.1.4)(vue@3.2.47): resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.0.0 vue: ^3.2.25 dependencies: - vite: 4.1.4_@types+node@18.15.3 + vite: 4.1.4(@types/node@18.15.3) vue: 3.2.47 dev: true - /@vue/compiler-core/3.2.47: + /@vue/compiler-core@3.2.47: resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==} dependencies: '@babel/parser': 7.21.3 @@ -535,13 +547,13 @@ packages: estree-walker: 2.0.2 source-map: 0.6.1 - /@vue/compiler-dom/3.2.47: + /@vue/compiler-dom@3.2.47: resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==} dependencies: '@vue/compiler-core': 3.2.47 '@vue/shared': 3.2.47 - /@vue/compiler-sfc/3.2.47: + /@vue/compiler-sfc@3.2.47: resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==} dependencies: '@babel/parser': 7.21.3 @@ -555,17 +567,17 @@ packages: postcss: 8.4.21 source-map: 0.6.1 - /@vue/compiler-ssr/3.2.47: + /@vue/compiler-ssr@3.2.47: resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==} dependencies: '@vue/compiler-dom': 3.2.47 '@vue/shared': 3.2.47 - /@vue/devtools-api/6.5.0: + /@vue/devtools-api@6.5.0: resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} dev: false - /@vue/eslint-config-typescript/11.0.2_75cttubc7yphoaabg6k3yrjnke: + /@vue/eslint-config-typescript@11.0.2(eslint-plugin-vue@9.9.0)(eslint@8.36.0)(typescript@4.9.5): resolution: {integrity: sha512-EiKud1NqlWmSapBFkeSrE994qpKx7/27uCGnhdqzllYDpQZroyX/O6bwjEpeuyKamvLbsGdO6PMR2faIf+zFnw==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: @@ -576,17 +588,17 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.55.0_342y7v4tc7ytrrysmit6jo4wri - '@typescript-eslint/parser': 5.55.0_vgl77cfdswitgr47lm5swmv43m + '@typescript-eslint/eslint-plugin': 5.55.0(@typescript-eslint/parser@5.55.0)(eslint@8.36.0)(typescript@4.9.5) + '@typescript-eslint/parser': 5.55.0(eslint@8.36.0)(typescript@4.9.5) eslint: 8.36.0 - eslint-plugin-vue: 9.9.0_eslint@8.36.0 + eslint-plugin-vue: 9.9.0(eslint@8.36.0) typescript: 4.9.5 - vue-eslint-parser: 9.1.0_eslint@8.36.0 + vue-eslint-parser: 9.1.0(eslint@8.36.0) transitivePeerDependencies: - supports-color dev: true - /@vue/reactivity-transform/3.2.47: + /@vue/reactivity-transform@3.2.47: resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==} dependencies: '@babel/parser': 7.21.3 @@ -595,25 +607,25 @@ packages: estree-walker: 2.0.2 magic-string: 0.25.9 - /@vue/reactivity/3.2.47: + /@vue/reactivity@3.2.47: resolution: {integrity: sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==} dependencies: '@vue/shared': 3.2.47 - /@vue/runtime-core/3.2.47: + /@vue/runtime-core@3.2.47: resolution: {integrity: sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==} dependencies: '@vue/reactivity': 3.2.47 '@vue/shared': 3.2.47 - /@vue/runtime-dom/3.2.47: + /@vue/runtime-dom@3.2.47: resolution: {integrity: sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==} dependencies: '@vue/runtime-core': 3.2.47 '@vue/shared': 3.2.47 csstype: 2.6.21 - /@vue/server-renderer/3.2.47_vue@3.2.47: + /@vue/server-renderer@3.2.47(vue@3.2.47): resolution: {integrity: sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==} peerDependencies: vue: 3.2.47 @@ -622,10 +634,10 @@ packages: '@vue/shared': 3.2.47 vue: 3.2.47 - /@vue/shared/3.2.47: + /@vue/shared@3.2.47: resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==} - /acorn-jsx/5.3.2_acorn@8.8.2: + /acorn-jsx@5.3.2(acorn@8.8.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -633,18 +645,18 @@ packages: acorn: 8.8.2 dev: true - /acorn-walk/8.2.0: + /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} dev: true - /acorn/8.8.2: + /acorn@8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} hasBin: true dev: true - /ajv/6.12.6: + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 @@ -653,19 +665,19 @@ packages: uri-js: 4.4.1 dev: true - /ansi-regex/5.0.1: + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} dev: true - /ansi-styles/4.3.0: + /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} dependencies: color-convert: 2.0.1 dev: true - /apexcharts/3.37.3: + /apexcharts@3.37.3: resolution: {integrity: sha512-+rnUui9uC3Mvh9qbQxUfqBnuJ0nAJOYTp+yKnA5bVmmndKXj5X/Q+OVIxkq0Jr5ysiK200Dsg1Tuz/OUG+DEpw==} dependencies: svg.draggable.js: 2.2.2 @@ -676,47 +688,47 @@ packages: svg.select.js: 3.0.1 dev: false - /arg/4.1.3: + /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: true - /argparse/2.0.1: + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true - /array-union/2.1.0: + /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} dev: true - /balanced-match/1.0.2: + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true - /boolbase/1.0.0: + /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: true - /brace-expansion/1.1.11: + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 dev: true - /braces/3.0.2: + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} dependencies: fill-range: 7.0.1 dev: true - /callsites/3.1.0: + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} dev: true - /chalk/4.1.2: + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} dependencies: @@ -724,26 +736,26 @@ packages: supports-color: 7.2.0 dev: true - /color-convert/2.0.1: + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 dev: true - /color-name/1.1.4: + /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true - /concat-map/0.0.1: + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /create-require/1.1.1: + /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true - /cross-spawn/7.0.3: + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} dependencies: @@ -752,16 +764,16 @@ packages: which: 2.0.2 dev: true - /cssesc/3.0.0: + /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true dev: true - /csstype/2.6.21: + /csstype@2.6.21: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} - /debug/4.3.4: + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} peerDependencies: @@ -773,30 +785,30 @@ packages: ms: 2.1.2 dev: true - /deep-is/0.1.4: + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /diff/4.0.2: + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} dev: true - /dir-glob/3.0.1: + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} dependencies: path-type: 4.0.0 dev: true - /doctrine/3.0.0: + /doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 dev: true - /esbuild/0.16.17: + /esbuild@0.16.17: resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} engines: {node: '>=12'} hasBin: true @@ -826,30 +838,30 @@ packages: '@esbuild/win32-x64': 0.16.17 dev: true - /escape-string-regexp/4.0.0: + /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} dev: true - /eslint-plugin-vue/9.9.0_eslint@8.36.0: + /eslint-plugin-vue@9.9.0(eslint@8.36.0): resolution: {integrity: sha512-YbubS7eK0J7DCf0U2LxvVP7LMfs6rC6UltihIgval3azO3gyDwEGVgsCMe1TmDiEkl6GdMKfRpaME6QxIYtzDQ==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 dependencies: eslint: 8.36.0 - eslint-utils: 3.0.0_eslint@8.36.0 + eslint-utils: 3.0.0(eslint@8.36.0) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.0.11 semver: 7.3.8 - vue-eslint-parser: 9.1.0_eslint@8.36.0 + vue-eslint-parser: 9.1.0(eslint@8.36.0) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color dev: true - /eslint-scope/5.1.1: + /eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} dependencies: @@ -857,7 +869,7 @@ packages: estraverse: 4.3.0 dev: true - /eslint-scope/7.1.1: + /eslint-scope@7.1.1: resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: @@ -865,7 +877,7 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.36.0: + /eslint-utils@3.0.0(eslint@8.36.0): resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: @@ -875,22 +887,22 @@ packages: eslint-visitor-keys: 2.1.0 dev: true - /eslint-visitor-keys/2.1.0: + /eslint-visitor-keys@2.1.0: resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} engines: {node: '>=10'} dev: true - /eslint-visitor-keys/3.3.0: + /eslint-visitor-keys@3.3.0: resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.36.0: + /eslint@8.36.0: resolution: {integrity: sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.2.0_eslint@8.36.0 + '@eslint-community/eslint-utils': 4.2.0(eslint@8.36.0) '@eslint-community/regexpp': 4.4.0 '@eslint/eslintrc': 2.0.1 '@eslint/js': 8.36.0 @@ -934,52 +946,52 @@ packages: - supports-color dev: true - /espree/9.5.0: + /espree@9.5.0: resolution: {integrity: sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.8.2 - acorn-jsx: 5.3.2_acorn@8.8.2 + acorn-jsx: 5.3.2(acorn@8.8.2) eslint-visitor-keys: 3.3.0 dev: true - /esquery/1.5.0: + /esquery@1.5.0: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 dev: true - /esrecurse/4.3.0: + /esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 dev: true - /estraverse/4.3.0: + /estraverse@4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} engines: {node: '>=4.0'} dev: true - /estraverse/5.3.0: + /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} dev: true - /estree-walker/2.0.2: + /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - /esutils/2.0.3: + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} dev: true - /fast-deep-equal/3.1.3: + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true - /fast-glob/3.2.12: + /fast-glob@3.2.12: resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} engines: {node: '>=8.6.0'} dependencies: @@ -990,35 +1002,35 @@ packages: micromatch: 4.0.5 dev: true - /fast-json-stable-stringify/2.1.0: + /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true - /fast-levenshtein/2.0.6: + /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /fastq/1.15.0: + /fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: reusify: 1.0.4 dev: true - /file-entry-cache/6.0.1: + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.0.4 dev: true - /fill-range/7.0.1: + /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 dev: true - /find-up/5.0.0: + /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} dependencies: @@ -1026,7 +1038,7 @@ packages: path-exists: 4.0.0 dev: true - /flat-cache/3.0.4: + /flat-cache@3.0.4: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: @@ -1034,15 +1046,15 @@ packages: rimraf: 3.0.2 dev: true - /flatted/3.2.7: + /flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true - /fs.realpath/1.0.0: + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true - /fsevents/2.3.2: + /fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -1050,25 +1062,25 @@ packages: dev: true optional: true - /function-bind/1.1.1: + /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true - /glob-parent/5.1.2: + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 dev: true - /glob-parent/6.0.2: + /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 dev: true - /glob/7.2.3: + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: fs.realpath: 1.0.0 @@ -1079,14 +1091,14 @@ packages: path-is-absolute: 1.0.1 dev: true - /globals/13.20.0: + /globals@13.20.0: resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 dev: true - /globby/11.1.0: + /globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} dependencies: @@ -1098,28 +1110,28 @@ packages: slash: 3.0.0 dev: true - /grapheme-splitter/1.0.4: + /grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true - /has-flag/4.0.0: + /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} dev: true - /has/1.0.3: + /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 dev: true - /ignore/5.2.4: + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} dev: true - /import-fresh/3.3.0: + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} dependencies: @@ -1127,74 +1139,74 @@ packages: resolve-from: 4.0.0 dev: true - /imurmurhash/0.1.4: + /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} dev: true - /inflight/1.0.6: + /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: once: 1.4.0 wrappy: 1.0.2 dev: true - /inherits/2.0.4: + /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true - /is-core-module/2.11.0: + /is-core-module@2.11.0: resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} dependencies: has: 1.0.3 dev: true - /is-extglob/2.1.1: + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} dev: true - /is-glob/4.0.3: + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 dev: true - /is-number/7.0.0: + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} dev: true - /is-path-inside/3.0.3: + /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} dev: true - /isexe/2.0.0: + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true - /js-sdsl/4.3.0: + /js-sdsl@4.3.0: resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} dev: true - /js-yaml/4.1.0: + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true dependencies: argparse: 2.0.1 dev: true - /json-schema-traverse/0.4.1: + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true - /json-stable-stringify-without-jsonify/1.0.1: + /json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true - /levn/0.4.1: + /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} dependencies: @@ -1202,43 +1214,43 @@ packages: type-check: 0.4.0 dev: true - /locate-path/6.0.0: + /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} dependencies: p-locate: 5.0.0 dev: true - /lodash.merge/4.6.2: + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true - /lodash/4.17.21: + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true - /lru-cache/6.0.0: + /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} dependencies: yallist: 4.0.0 dev: true - /magic-string/0.25.9: + /magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} dependencies: sourcemap-codec: 1.4.8 - /make-error/1.3.6: + /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true - /merge2/1.4.1: + /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} dev: true - /micromatch/4.0.5: + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} dependencies: @@ -1246,42 +1258,42 @@ packages: picomatch: 2.3.1 dev: true - /minimatch/3.1.2: + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 dev: true - /ms/2.1.2: + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true - /nanoid/3.3.4: + /nanoid@3.3.4: resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - /natural-compare-lite/1.4.0: + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true - /natural-compare/1.4.0: + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /nth-check/2.1.1: + /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: boolbase: 1.0.0 dev: true - /once/1.4.0: + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 dev: true - /optionator/0.9.1: + /optionator@0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} dependencies: @@ -1293,60 +1305,60 @@ packages: word-wrap: 1.2.3 dev: true - /p-limit/3.1.0: + /p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 dev: true - /p-locate/5.0.0: + /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} dependencies: p-limit: 3.1.0 dev: true - /parent-module/1.0.1: + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} dependencies: callsites: 3.1.0 dev: true - /path-exists/4.0.0: + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} dev: true - /path-is-absolute/1.0.1: + /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} dev: true - /path-key/3.1.1: + /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} dev: true - /path-parse/1.0.7: + /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true - /path-type/4.0.0: + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} dev: true - /picocolors/1.0.0: + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - /picomatch/2.3.1: + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} dev: true - /postcss-selector-parser/6.0.11: + /postcss-selector-parser@6.0.11: resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} engines: {node: '>=4'} dependencies: @@ -1354,7 +1366,7 @@ packages: util-deprecate: 1.0.2 dev: true - /postcss/8.4.21: + /postcss@8.4.21: resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} engines: {node: ^10 || ^12 || >=14} dependencies: @@ -1362,26 +1374,26 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 - /prelude-ls/1.2.1: + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} dev: true - /punycode/2.3.0: + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} dev: true - /queue-microtask/1.2.3: + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true - /resolve-from/4.0.0: + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} dev: true - /resolve/1.22.1: + /resolve@1.22.1: resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} hasBin: true dependencies: @@ -1390,19 +1402,19 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true - /reusify/1.0.4: + /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true - /rimraf/3.0.2: + /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true dependencies: glob: 7.2.3 dev: true - /rollup/3.19.1: + /rollup@3.19.1: resolution: {integrity: sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true @@ -1410,13 +1422,13 @@ packages: fsevents: 2.3.2 dev: true - /run-parallel/1.2.0: + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 dev: true - /semver/7.3.8: + /semver@7.3.8: resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} engines: {node: '>=10'} hasBin: true @@ -1424,92 +1436,92 @@ packages: lru-cache: 6.0.0 dev: true - /shebang-command/2.0.0: + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 dev: true - /shebang-regex/3.0.0: + /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} dev: true - /slash/3.0.0: + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} dev: true - /source-map-js/1.0.2: + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - /source-map/0.6.1: + /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - /sourcemap-codec/1.4.8: + /sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead - /strip-ansi/6.0.1: + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 dev: true - /strip-json-comments/3.1.1: + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} dev: true - /supports-color/7.2.0: + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} dependencies: has-flag: 4.0.0 dev: true - /supports-preserve-symlinks-flag/1.0.0: + /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} dev: true - /svg.draggable.js/2.2.2: + /svg.draggable.js@2.2.2: resolution: {integrity: sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==} engines: {node: '>= 0.8.0'} dependencies: svg.js: 2.7.1 dev: false - /svg.easing.js/2.0.0: + /svg.easing.js@2.0.0: resolution: {integrity: sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==} engines: {node: '>= 0.8.0'} dependencies: svg.js: 2.7.1 dev: false - /svg.filter.js/2.0.2: + /svg.filter.js@2.0.2: resolution: {integrity: sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==} engines: {node: '>= 0.8.0'} dependencies: svg.js: 2.7.1 dev: false - /svg.js/2.7.1: + /svg.js@2.7.1: resolution: {integrity: sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==} dev: false - /svg.pathmorphing.js/0.1.3: + /svg.pathmorphing.js@0.1.3: resolution: {integrity: sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==} engines: {node: '>= 0.8.0'} dependencies: svg.js: 2.7.1 dev: false - /svg.resize.js/1.4.3: + /svg.resize.js@1.4.3: resolution: {integrity: sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==} engines: {node: '>= 0.8.0'} dependencies: @@ -1517,36 +1529,36 @@ packages: svg.select.js: 2.1.2 dev: false - /svg.select.js/2.1.2: + /svg.select.js@2.1.2: resolution: {integrity: sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==} engines: {node: '>= 0.8.0'} dependencies: svg.js: 2.7.1 dev: false - /svg.select.js/3.0.1: + /svg.select.js@3.0.1: resolution: {integrity: sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==} engines: {node: '>= 0.8.0'} dependencies: svg.js: 2.7.1 dev: false - /text-table/0.2.0: + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true - /to-fast-properties/2.0.0: + /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - /to-regex-range/5.0.1: + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 dev: true - /ts-node/10.9.1_cbfmry4sbbh4vatmdrsmatfg5a: + /ts-node@10.9.1(@types/node@18.15.3)(typescript@4.9.5): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -1577,11 +1589,11 @@ packages: yn: 3.1.1 dev: true - /tslib/1.14.1: + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tsutils/3.21.0_typescript@4.9.5: + /tsutils@3.21.0(typescript@4.9.5): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: @@ -1591,38 +1603,38 @@ packages: typescript: 4.9.5 dev: true - /type-check/0.4.0: + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 dev: true - /type-fest/0.20.2: + /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} dev: true - /typescript/4.9.5: + /typescript@4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} hasBin: true - /uri-js/4.4.1: + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.0 dev: true - /util-deprecate/1.0.2: + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true - /v8-compile-cache-lib/3.0.1: + /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true - /vite/4.1.4_@types+node@18.15.3: + /vite@4.1.4(@types/node@18.15.3): resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -1656,7 +1668,7 @@ packages: fsevents: 2.3.2 dev: true - /vue-eslint-parser/9.1.0_eslint@8.36.0: + /vue-eslint-parser@9.1.0(eslint@8.36.0): resolution: {integrity: sha512-NGn/iQy8/Wb7RrRa4aRkokyCZfOUWk19OP5HP6JEozQFX5AoS/t+Z0ZN7FY4LlmWc4FNI922V7cvX28zctN8dQ==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: @@ -1674,7 +1686,7 @@ packages: - supports-color dev: true - /vue-router/4.1.6_vue@3.2.47: + /vue-router@4.1.6(vue@3.2.47): resolution: {integrity: sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==} peerDependencies: vue: ^3.2.0 @@ -1683,16 +1695,7 @@ packages: vue: 3.2.47 dev: false - /vue/3.2.47: - resolution: {integrity: sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==} - dependencies: - '@vue/compiler-dom': 3.2.47 - '@vue/compiler-sfc': 3.2.47 - '@vue/runtime-dom': 3.2.47 - '@vue/server-renderer': 3.2.47_vue@3.2.47 - '@vue/shared': 3.2.47 - - /vue3-apexcharts/1.4.1_w3ch7w2j635wji4x6c2377ue4i: + /vue3-apexcharts@1.4.1(apexcharts@3.37.3)(vue@3.2.47): resolution: {integrity: sha512-96qP8JDqB9vwU7bkG5nVU+E0UGQn7yYQVqUUCLQMYWDuQyu2vE77H/UFZ1yI+hwzlSTBKT9BqnNG8JsFegB3eg==} peerDependencies: apexcharts: '> 3.0.0' @@ -1702,7 +1705,16 @@ packages: vue: 3.2.47 dev: false - /which/2.0.2: + /vue@3.2.47: + resolution: {integrity: sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==} + dependencies: + '@vue/compiler-dom': 3.2.47 + '@vue/compiler-sfc': 3.2.47 + '@vue/runtime-dom': 3.2.47 + '@vue/server-renderer': 3.2.47(vue@3.2.47) + '@vue/shared': 3.2.47 + + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true @@ -1710,30 +1722,30 @@ packages: isexe: 2.0.0 dev: true - /word-wrap/1.2.3: + /word-wrap@1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} dev: true - /wrappy/1.0.2: + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true - /xml-name-validator/4.0.0: + /xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} dev: true - /yallist/4.0.0: + /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true - /yn/3.1.1: + /yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} dev: true - /yocto-queue/0.1.0: + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true diff --git a/Epsilon.Host.Frontend/src/components/Competance/KPIMatrix.vue b/Epsilon.Host.Frontend/src/components/Competance/KPIMatrix.vue new file mode 100644 index 00000000..14d2540c --- /dev/null +++ b/Epsilon.Host.Frontend/src/components/Competance/KPIMatrix.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/Epsilon.Host.Frontend/src/components/Competance/KpiMatrix.vue b/Epsilon.Host.Frontend/src/components/Competance/KpiMatrix.vue deleted file mode 100644 index d7222343..00000000 --- a/Epsilon.Host.Frontend/src/components/Competance/KpiMatrix.vue +++ /dev/null @@ -1,100 +0,0 @@ - - - - - diff --git a/Epsilon.Host.Frontend/src/components/Competance/KpiTable.vue b/Epsilon.Host.Frontend/src/components/Competance/KpiTable.vue deleted file mode 100644 index 889f0d1a..00000000 --- a/Epsilon.Host.Frontend/src/components/Competance/KpiTable.vue +++ /dev/null @@ -1,84 +0,0 @@ - - - - - diff --git a/Epsilon.Host.Frontend/src/components/KpiLegend/KpiLegend.vue b/Epsilon.Host.Frontend/src/components/KpiLegend/KpiLegend.vue deleted file mode 100644 index c47cb156..00000000 --- a/Epsilon.Host.Frontend/src/components/KpiLegend/KpiLegend.vue +++ /dev/null @@ -1,67 +0,0 @@ - - - - - diff --git a/Epsilon.Host.Frontend/src/logic/Api.ts b/Epsilon.Host.Frontend/src/logic/Api.ts index f35e2098..c04b7680 100644 --- a/Epsilon.Host.Frontend/src/logic/Api.ts +++ b/Epsilon.Host.Frontend/src/logic/Api.ts @@ -10,469 +10,439 @@ */ export interface Activity { - name?: string | null; - color?: string | null; + /** @format int32 */ + id?: number; + name?: string | null; + color?: string | null; } export interface ArchitectureLayer { - name?: string | null; - shortName?: string | null; - color?: string | null; -} - -export interface AssessmentRating { - /** @format double */ - points?: number | null; - outcome?: Outcome; -} - -export interface Assignment { - name?: string | null; - modules?: Module[] | null; + /** @format int32 */ + id?: number; + name?: string | null; + shortName?: string | null; + color?: string | null; } export interface CompetenceProfile { - hboIDomain?: HboIDomain; - professionalTaskOutcomes?: ProfessionalTaskOutcome[] | null; - professionalSkillOutcomes?: ProfessionalSkillOutcome[] | null; -} - -export interface Course { - name?: string | null; - submissionsConnection?: SubmissionsConnection; + hboIDomain?: IHboIDomain; + professionalTaskOutcomes?: ProfessionalTaskResult[] | null; + professionalSkillOutcomes?: ProfessionalSkillResult[] | null; + terms?: EnrollmentTerm[] | null; + decayingAveragesPerTask?: DecayingAveragePerLayer[] | null; + decayingAveragesPerSkill?: DecayingAveragePerSkill[] | null; } -export interface CourseData { - course?: Course; +export interface DecayingAveragePerActivity { + /** @format int32 */ + activity?: number; + /** @format double */ + decayingAverage?: number; } -export interface GetUserSubmissionOutcomes { - data?: CourseData; +export interface DecayingAveragePerLayer { + /** @format int32 */ + architectureLayer?: number; + layerActivities?: DecayingAveragePerActivity[] | null; } -export interface HboIDomain { - architectureLayers?: Record; - activities?: Record; - professionalSkills?: Record; - masteryLevels?: Record; +export interface DecayingAveragePerSkill { + /** @format int32 */ + skill?: number; + /** @format double */ + decayingAverage?: number; } -export interface LtiOpenIdConnectCallback { - authenticityToken?: string | null; - idToken?: string | null; - state?: string | null; - ltiStorageTarget?: string | null; +export interface EnrollmentTerm { + name?: string | null; + /** @format date-time */ + start_at?: string | null; + /** @format date-time */ + end_at?: string | null; } -export interface LtiOpenIdConnectLaunch { - issuer?: string | null; - loginHint?: string | null; - clientId?: string | null; - /** @format uri */ - targetLinkUri?: string | null; - encodedLtiMessageHint?: string | null; - ltiStorageTarget?: string | null; +export interface IHboIDomain { + architectureLayers?: ArchitectureLayer[] | null; + activities?: Activity[] | null; + professionalSkills?: ProfessionalSkill[] | null; + masteryLevels?: MasteryLevel[] | null; } export interface MasteryLevel { - /** @format int32 */ - level?: number; - color?: string | null; -} - -export interface Module { - name?: string | null; -} - -export interface Node { - assignment?: Assignment; - rubricAssessmentsConnection?: RubricAssessmentsConnection; -} - -export interface Outcome { - title?: string | null; + /** @format int32 */ + id?: number; + /** @format int32 */ + level?: number; + color?: string | null; } export interface ProfessionalSkill { - name?: string | null; - shortName?: string | null; - color?: string | null; + /** @format int32 */ + id?: number; + name?: string | null; + shortName?: string | null; + color?: string | null; } -export interface ProfessionalSkillOutcome { - /** @format int32 */ - professionalSkillId?: number; - /** @format int32 */ - masteryLevel?: number; - /** @format int32 */ - grade?: number; - /** @format date-time */ - assessedAt?: string; +export interface ProfessionalSkillResult { + /** @format int32 */ + skill?: number; + /** @format int32 */ + masteryLevel?: number; + /** @format double */ + grade?: number; + /** @format date-time */ + assessedAt?: string; } -export interface ProfessionalTaskOutcome { - /** @format int32 */ - architectureLayerId?: number; - /** @format int32 */ - activityId?: number; - /** @format int32 */ - masteryLevelId?: number; - /** @format int32 */ - grade?: number; - /** @format date-time */ - assessedAt?: string; -} - -export interface RubricAssessmentNode { - assessmentRatings?: AssessmentRating[] | null; - user?: User; -} - -export interface RubricAssessmentsConnection { - nodes?: RubricAssessmentNode[] | null; -} - -export interface SubmissionsConnection { - nodes?: Node[] | null; -} - -export interface User { - name?: string | null; +export interface ProfessionalTaskResult { + /** @format int32 */ + architectureLayer?: number; + /** @format int32 */ + activity?: number; + /** @format int32 */ + masteryLevel?: number; + /** @format double */ + grade?: number; + /** @format date-time */ + assessedAt?: string; } export type QueryParamsType = Record; export type ResponseFormat = keyof Omit; export interface FullRequestParams extends Omit { - /** set parameter to `true` for call `securityWorker` for this request */ - secure?: boolean; - /** request path */ - path: string; - /** content type of request body */ - type?: ContentType; - /** query params */ - query?: QueryParamsType; - /** format of response (i.e. response.json() -> format: "json") */ - format?: ResponseFormat; - /** request body */ - body?: unknown; - /** base url */ - baseUrl?: string; - /** request cancellation token */ - cancelToken?: CancelToken; + /** set parameter to `true` for call `securityWorker` for this request */ + secure?: boolean; + /** request path */ + path: string; + /** content type of request body */ + type?: ContentType; + /** query params */ + query?: QueryParamsType; + /** format of response (i.e. response.json() -> format: "json") */ + format?: ResponseFormat; + /** request body */ + body?: unknown; + /** base url */ + baseUrl?: string; + /** request cancellation token */ + cancelToken?: CancelToken; } -export type RequestParams = Omit; +export type RequestParams = Omit< + FullRequestParams, + "body" | "method" | "query" | "path" +>; export interface ApiConfig { - baseUrl?: string; - baseApiParams?: Omit; - securityWorker?: (securityData: SecurityDataType | null) => Promise | RequestParams | void; - customFetch?: typeof fetch; + baseUrl?: string; + baseApiParams?: Omit; + securityWorker?: ( + securityData: SecurityDataType | null + ) => Promise | RequestParams | void; + customFetch?: typeof fetch; } -export interface HttpResponse extends Response { - data: D; - error: E; +export interface HttpResponse + extends Response { + data: D; + error: E; } type CancelToken = Symbol | string | number; export enum ContentType { - Json = "application/json", - FormData = "multipart/form-data", - UrlEncoded = "application/x-www-form-urlencoded", - Text = "text/plain", + Json = "application/json", + FormData = "multipart/form-data", + UrlEncoded = "application/x-www-form-urlencoded", + Text = "text/plain", } export class HttpClient { - public baseUrl: string = "https://localhost:7084"; - private securityData: SecurityDataType | null = null; - private securityWorker?: ApiConfig["securityWorker"]; - private abortControllers = new Map(); - private customFetch = (...fetchParams: Parameters) => fetch(...fetchParams); - - private baseApiParams: RequestParams = { - credentials: "same-origin", - headers: {}, - redirect: "follow", - referrerPolicy: "no-referrer", - }; - - constructor(apiConfig: ApiConfig = {}) { - Object.assign(this, apiConfig); - } - - public setSecurityData = (data: SecurityDataType | null) => { - this.securityData = data; - }; - - protected encodeQueryParam(key: string, value: any) { - const encodedKey = encodeURIComponent(key); - return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`; - } - - protected addQueryParam(query: QueryParamsType, key: string) { - return this.encodeQueryParam(key, query[key]); - } - - protected addArrayQueryParam(query: QueryParamsType, key: string) { - const value = query[key]; - return value.map((v: any) => this.encodeQueryParam(key, v)).join("&"); - } - - protected toQueryString(rawQuery?: QueryParamsType): string { - const query = rawQuery || {}; - const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]); - return keys - .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key))) - .join("&"); - } - - protected addQueryParams(rawQuery?: QueryParamsType): string { - const queryString = this.toQueryString(rawQuery); - return queryString ? `?${queryString}` : ""; - } - - private contentFormatters: Record any> = { - [ContentType.Json]: (input: any) => - input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input, - [ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input), - [ContentType.FormData]: (input: any) => - Object.keys(input || {}).reduce((formData, key) => { - const property = input[key]; - formData.append( - key, - property instanceof Blob - ? property - : typeof property === "object" && property !== null - ? JSON.stringify(property) - : `${property}`, - ); - return formData; - }, new FormData()), - [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input), - }; - - protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams { - return { - ...this.baseApiParams, - ...params1, - ...(params2 || {}), - headers: { - ...(this.baseApiParams.headers || {}), - ...(params1.headers || {}), - ...((params2 && params2.headers) || {}), - }, + public baseUrl: string = "https://localhost:7084"; + private securityData: SecurityDataType | null = null; + private securityWorker?: ApiConfig["securityWorker"]; + private abortControllers = new Map(); + private customFetch = (...fetchParams: Parameters) => + fetch(...fetchParams); + + private baseApiParams: RequestParams = { + credentials: "same-origin", + headers: {}, + redirect: "follow", + referrerPolicy: "no-referrer", }; - } - protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => { - if (this.abortControllers.has(cancelToken)) { - const abortController = this.abortControllers.get(cancelToken); - if (abortController) { - return abortController.signal; - } - return void 0; + constructor(apiConfig: ApiConfig = {}) { + Object.assign(this, apiConfig); + } + + public setSecurityData = (data: SecurityDataType | null) => { + this.securityData = data; + }; + + protected encodeQueryParam(key: string, value: any) { + const encodedKey = encodeURIComponent(key); + return `${encodedKey}=${encodeURIComponent( + typeof value === "number" ? value : `${value}` + )}`; + } + + protected addQueryParam(query: QueryParamsType, key: string) { + return this.encodeQueryParam(key, query[key]); + } + + protected addArrayQueryParam(query: QueryParamsType, key: string) { + const value = query[key]; + return value.map((v: any) => this.encodeQueryParam(key, v)).join("&"); + } + + protected toQueryString(rawQuery?: QueryParamsType): string { + const query = rawQuery || {}; + const keys = Object.keys(query).filter( + (key) => "undefined" !== typeof query[key] + ); + return keys + .map((key) => + Array.isArray(query[key]) + ? this.addArrayQueryParam(query, key) + : this.addQueryParam(query, key) + ) + .join("&"); } - const abortController = new AbortController(); - this.abortControllers.set(cancelToken, abortController); - return abortController.signal; - }; + protected addQueryParams(rawQuery?: QueryParamsType): string { + const queryString = this.toQueryString(rawQuery); + return queryString ? `?${queryString}` : ""; + } - public abortRequest = (cancelToken: CancelToken) => { - const abortController = this.abortControllers.get(cancelToken); + private contentFormatters: Record any> = { + [ContentType.Json]: (input: any) => + input !== null && + (typeof input === "object" || typeof input === "string") + ? JSON.stringify(input) + : input, + [ContentType.Text]: (input: any) => + input !== null && typeof input !== "string" + ? JSON.stringify(input) + : input, + [ContentType.FormData]: (input: any) => + Object.keys(input || {}).reduce((formData, key) => { + const property = input[key]; + formData.append( + key, + property instanceof Blob + ? property + : typeof property === "object" && property !== null + ? JSON.stringify(property) + : `${property}` + ); + return formData; + }, new FormData()), + [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input), + }; - if (abortController) { - abortController.abort(); - this.abortControllers.delete(cancelToken); + protected mergeRequestParams( + params1: RequestParams, + params2?: RequestParams + ): RequestParams { + return { + ...this.baseApiParams, + ...params1, + ...(params2 || {}), + headers: { + ...(this.baseApiParams.headers || {}), + ...(params1.headers || {}), + ...((params2 && params2.headers) || {}), + }, + }; } - }; - - public request = async ({ - body, - secure, - path, - type, - query, - format, - baseUrl, - cancelToken, - ...params - }: FullRequestParams): Promise> => { - const secureParams = - ((typeof secure === "boolean" ? secure : this.baseApiParams.secure) && - this.securityWorker && - (await this.securityWorker(this.securityData))) || - {}; - const requestParams = this.mergeRequestParams(params, secureParams); - const queryString = query && this.toQueryString(query); - const payloadFormatter = this.contentFormatters[type || ContentType.Json]; - const responseFormat = format || requestParams.format; - - return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, { - ...requestParams, - headers: { - ...(requestParams.headers || {}), - ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}), - }, - signal: cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal, - body: typeof body === "undefined" || body === null ? null : payloadFormatter(body), - }).then(async (response) => { - const r = response as HttpResponse; - r.data = null as unknown as T; - r.error = null as unknown as E; - - const data = !responseFormat - ? r - : await response[responseFormat]() - .then((data) => { - if (r.ok) { - r.data = data; - } else { - r.error = data; - } - return r; - }) - .catch((e) => { - r.error = e; - return r; - }); - - if (cancelToken) { - this.abortControllers.delete(cancelToken); - } - - if (!response.ok) throw data; - return data; - }); - }; + + protected createAbortSignal = ( + cancelToken: CancelToken + ): AbortSignal | undefined => { + if (this.abortControllers.has(cancelToken)) { + const abortController = this.abortControllers.get(cancelToken); + if (abortController) { + return abortController.signal; + } + return void 0; + } + + const abortController = new AbortController(); + this.abortControllers.set(cancelToken, abortController); + return abortController.signal; + }; + + public abortRequest = (cancelToken: CancelToken) => { + const abortController = this.abortControllers.get(cancelToken); + + if (abortController) { + abortController.abort(); + this.abortControllers.delete(cancelToken); + } + }; + + public request = async ({ + body, + secure, + path, + type, + query, + format, + baseUrl, + cancelToken, + ...params + }: FullRequestParams): Promise> => { + const secureParams = + ((typeof secure === "boolean" + ? secure + : this.baseApiParams.secure) && + this.securityWorker && + (await this.securityWorker(this.securityData))) || + {}; + const requestParams = this.mergeRequestParams(params, secureParams); + const queryString = query && this.toQueryString(query); + const payloadFormatter = + this.contentFormatters[type || ContentType.Json]; + const responseFormat = format || requestParams.format; + + return this.customFetch( + `${baseUrl || this.baseUrl || ""}${path}${ + queryString ? `?${queryString}` : "" + }`, + { + ...requestParams, + headers: { + ...(requestParams.headers || {}), + ...(type && type !== ContentType.FormData + ? { "Content-Type": type } + : {}), + }, + signal: cancelToken + ? this.createAbortSignal(cancelToken) + : requestParams.signal, + body: + typeof body === "undefined" || body === null + ? null + : payloadFormatter(body), + } + ).then(async (response) => { + const r = response as HttpResponse; + r.data = null as unknown as T; + r.error = null as unknown as E; + + const data = !responseFormat + ? r + : await response[responseFormat]() + .then((data) => { + if (r.ok) { + r.data = data; + } else { + r.error = data; + } + return r; + }) + .catch((e) => { + r.error = e; + return r; + }); + + if (cancelToken) { + this.abortControllers.delete(cancelToken); + } + + if (!response.ok) throw data; + return data; + }); + }; } /** * @title Epsilon.Host.WebApi * @version 1.0 */ -export class Api extends HttpClient { - auth = { - /** - * No description - * - * @tags Auth - * @name ChallengeList - * @request GET:/Auth/challenge - */ - challengeList: (params: RequestParams = {}) => - this.request({ - path: `/Auth/challenge`, - method: "GET", - ...params, - }), - - /** - * No description - * - * @tags Auth - * @name CallbackList - * @request GET:/Auth/callback - */ - callbackList: (params: RequestParams = {}) => - this.request({ - path: `/Auth/callback`, - method: "GET", - ...params, - }), - }; - component = { - /** - * No description - * - * @tags Component - * @name CompetenceProfileList - * @request GET:/Component/competence_profile - */ - competenceProfileList: ( - query?: { - courseId?: string; - /** @format date-time */ - startDate?: string; - /** @format date-time */ - endDate?: string; - }, - params: RequestParams = {}, - ) => - this.request({ - path: `/Component/competence_profile`, - method: "GET", - query: query, - format: "json", - ...params, - }), - - /** - * No description - * - * @tags Component - * @name CompetenceProfileMockList - * @request GET:/Component/competence_profile_mock - */ - competenceProfileMockList: ( - query?: { - /** @format date-time */ - startDate?: string; - /** @format date-time */ - endDate?: string; - }, - params: RequestParams = {}, - ) => - this.request({ - path: `/Component/competence_profile_mock`, - method: "GET", - query: query, - format: "json", - ...params, - }), - }; - lti = { - /** - * No description - * - * @tags Lti - * @name OidcAuthCreate - * @request POST:/lti/oidc/auth - */ - oidcAuthCreate: ( - query?: { - launchRequest?: LtiOpenIdConnectLaunch; - }, - params: RequestParams = {}, - ) => - this.request({ - path: `/lti/oidc/auth`, - method: "POST", - query: query, - ...params, - }), - - /** - * No description - * - * @tags Lti - * @name OidcCallbackCreate - * @request POST:/lti/oidc/callback - */ - oidcCallbackCreate: ( - query?: { - callback?: LtiOpenIdConnectCallback; - }, - params: RequestParams = {}, - ) => - this.request({ - path: `/lti/oidc/callback`, - method: "POST", - query: query, - ...params, - }), - }; +export class Api< + SecurityDataType extends unknown +> extends HttpClient { + auth = { + /** + * No description + * + * @tags Auth + * @name ChallengeList + * @request GET:/Auth/challenge + */ + challengeList: (params: RequestParams = {}) => + this.request({ + path: `/Auth/challenge`, + method: "GET", + ...params, + }), + + /** + * No description + * + * @tags Auth + * @name CallbackList + * @request GET:/Auth/callback + */ + callbackList: (params: RequestParams = {}) => + this.request({ + path: `/Auth/callback`, + method: "GET", + ...params, + }), + }; + component = { + /** + * No description + * + * @tags Component + * @name ComponentDetail + * @request GET:/Component/{componentName} + */ + componentDetail: ( + componentName: string, + query?: { + /** @format date-time */ + startDate?: string; + /** @format date-time */ + endDate?: string; + }, + params: RequestParams = {} + ) => + this.request({ + path: `/Component/${componentName}`, + method: "GET", + query: query, + format: "json", + ...params, + }), + }; + document = { + /** + * No description + * + * @tags Document + * @name WordList + * @request GET:/Document/word + */ + wordList: ( + query?: { + /** @format date-time */ + startDate?: string; + /** @format date-time */ + endDate?: string; + }, + params: RequestParams = {} + ) => + this.request({ + path: `/Document/word`, + method: "GET", + query: query, + ...params, + }), + }; } diff --git a/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue b/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue index 87f187e3..61dda17a 100644 --- a/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue +++ b/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue @@ -1,58 +1,22 @@ diff --git a/Epsilon.Host.WebApi/CompetenceDocument.docx b/Epsilon.Host.WebApi/CompetenceDocument.docx deleted file mode 100644 index e69de29b..00000000 diff --git a/Epsilon.Host.WebApi/Controllers/ComponentController.cs b/Epsilon.Host.WebApi/Controllers/ComponentController.cs index dbd2e63b..862aa6c7 100644 --- a/Epsilon.Host.WebApi/Controllers/ComponentController.cs +++ b/Epsilon.Host.WebApi/Controllers/ComponentController.cs @@ -1,8 +1,5 @@ using Epsilon.Abstractions.Component; -using Epsilon.Abstractions.Model; -using Epsilon.Canvas; -using Epsilon.Canvas.Abstractions.QueryResponse; -using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Abstractions.Service; using Microsoft.AspNetCore.Mvc; namespace Epsilon.Host.WebApi.Controllers; @@ -11,29 +8,23 @@ namespace Epsilon.Host.WebApi.Controllers; [Route("[controller]")] public class ComponentController : ControllerBase { - private readonly IGraphQlHttpService _graphQlService; - private readonly IConfiguration _configuration; - private readonly ICompetenceProfileConverter _competenceProfileConverter; - private readonly IAccountHttpService _accountHttpService; + private readonly ICompetenceComponentService _competenceComponentService; - public ComponentController(IGraphQlHttpService graphQlService, IConfiguration configuration, ICompetenceProfileConverter competenceProfileConverter, IAccountHttpService accountHttpService) + public ComponentController(ICompetenceComponentService competenceComponentService) { - _graphQlService = graphQlService; - _configuration = configuration; - _competenceProfileConverter = competenceProfileConverter; - _accountHttpService = accountHttpService; + _competenceComponentService = competenceComponentService; } - [HttpGet("competence_profile")] - public async Task> GetCompetenceProfile() + [HttpGet("{componentName}")] + [Produces(typeof(CompetenceProfile))] + public async Task> GetCompetenceProfile(string componentName, DateTime startDate, DateTime endDate) { - var studentId = _configuration["Canvas:StudentId"]; - var query = QueryConstants.GetAllUserCoursesSubmissionOutcomes.Replace("$studentIds", $"{studentId}"); - var queryResult = await _graphQlService.Query(query); + var component = await _competenceComponentService.GetComponent(componentName, startDate, endDate); + if (component == null) + { + return NotFound(); + } - var terms = await _accountHttpService.GetAllTerms(1); - - var competenceProfile = _competenceProfileConverter.ConvertFrom(queryResult, new HboIDomain2018(), terms); - return competenceProfile; + return Ok(component); } } \ No newline at end of file diff --git a/Epsilon.Host.WebApi/Controllers/DocumentController.cs b/Epsilon.Host.WebApi/Controllers/DocumentController.cs index 934e01ff..ac7bb37a 100644 --- a/Epsilon.Host.WebApi/Controllers/DocumentController.cs +++ b/Epsilon.Host.WebApi/Controllers/DocumentController.cs @@ -1,114 +1,29 @@ -using Epsilon.Abstractions.Model; -using Epsilon.Export.Exporters; -using Epsilon.Host.WebApi.Models; -using Epsilon.Host.WebApi.Responses; +using Epsilon.Abstractions.Service; using Microsoft.AspNetCore.Mvc; namespace Epsilon.Host.WebApi.Controllers; [ApiController] -[Route("api/document/{documentId:int}/")] +[Route("[controller]")] public class DocumentController : Controller { - private static int GetRandomNumber() - { - return new Random().Next(0, 10); - } + private readonly ICompetenceDocumentService _competenceDocumentService; - [HttpGet("structure")] - public IActionResult GetDocumentStructure(int documentId) + public DocumentController(ICompetenceDocumentService competenceDocumentService) { - return Ok(new GetDocumentStructureResponse - { - DocumentId = documentId, - ComponentIds = new[] { GetRandomNumber(), GetRandomNumber(), GetRandomNumber() } - }); + _competenceDocumentService = competenceDocumentService; } - - [HttpGet("word")] - public async Task GetWord(int documentId) - { - var wordExporter = new WordModuleExporter(); - var wordContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; - var stream = await wordExporter.Export(new ExportData - { - CourseModules = new [] - { - new CourseModule - { - Name = "Module 1", - Outcomes = new List - { - new CourseOutcome - { - Name = "Outcome 1", - Description = "Description 1", - Assignments = new List - { - new CourseAssignment - { - Name = "Assignment 1", - Url = "https://google.com/", - Score = "Outstanding" - } - } - } - } - } - } - }, "word"); - - // Reset position to zero to prepare for copy - stream.Position = 0; - return File(stream, wordContentType, "CompetenceDocument.docx"); - } - - [HttpGet("home-page")] - public IActionResult GetHomePage(int documentId) + [HttpGet("word")] + public async Task DownloadWordDocument(DateTime startDate, DateTime endDate) { - return Ok(new GetDocumentHomePageResponse - { - DocumentId = documentId, - HomePage = new HomePage - { - Id = 2, - Type = "homepage", - Title = "Homepage", - Description = "The homepage of the website", - Config = new Dictionary - { - { "HomePageRecord", "Jelle" }, - }, - Data = new HomePageData - { - SomeHomePageDataArray = new[] { "red", "green", "blue" } - } - } - }); - } + var stream = new MemoryStream(); + await _competenceDocumentService.WriteDocument(stream, startDate, endDate); - [HttpGet("kpi-matrix")] - public IActionResult GetKpiMatrix(int documentId) - { - return Ok(new GetDocumentKpiMatrixResponse - { - DocumentId = documentId, - KpiMatrix = new KpiMatrix - { - Id = 1, - Type = "kpi-matrix", - Title = "KPI Matrix", - Description = "A matrix of KPIs", - Config = new Dictionary - { - { "KpiConfigRecord", "Sven" }, - }, - Data = new KpiMatrixData - { - SomeKpiMatrixDataArray = new[] { "red", "green", "blue" } - } - }, - }); + return File( + stream, + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "CompetenceDocument.docx" + ); } } \ No newline at end of file diff --git a/Epsilon.Host.WebApi/Epsilon.Host.WebApi.csproj b/Epsilon.Host.WebApi/Epsilon.Host.WebApi.csproj index 205d600c..876338de 100644 --- a/Epsilon.Host.WebApi/Epsilon.Host.WebApi.csproj +++ b/Epsilon.Host.WebApi/Epsilon.Host.WebApi.csproj @@ -14,4 +14,10 @@ + + + Never + + + diff --git a/Epsilon.Host.WebApi/Interfaces/IComponent.cs b/Epsilon.Host.WebApi/Interfaces/IComponent.cs deleted file mode 100644 index e93ac97c..00000000 --- a/Epsilon.Host.WebApi/Interfaces/IComponent.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Epsilon.Host.WebApi.Interfaces; - -public interface IComponent -{ - int Id { get; set; } - string Type { get; set; } - string Title { get; set; } - string Description { get; set; } - IDictionary Config { get; set; } -} - -public interface IComponent : IComponent -{ - TData Data { get; set; } -} \ No newline at end of file diff --git a/Epsilon.Host.WebApi/Models/Document.cs b/Epsilon.Host.WebApi/Models/Document.cs deleted file mode 100644 index 9293ee06..00000000 --- a/Epsilon.Host.WebApi/Models/Document.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Epsilon.Host.WebApi.Models; - -public record CompetenceDocument -{ - public HomePage HomePage { get; set; } - public KpiMatrix KpiMatrix { get; set; } -} \ No newline at end of file diff --git a/Epsilon.Host.WebApi/Models/Homepage.cs b/Epsilon.Host.WebApi/Models/Homepage.cs deleted file mode 100644 index 92afb49e..00000000 --- a/Epsilon.Host.WebApi/Models/Homepage.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Epsilon.Host.WebApi.Interfaces; - -namespace Epsilon.Host.WebApi.Models; - -public record HomePage : IComponent -{ - public int Id { get; set; } - public string Type { get; set; } - public string Title { get; set; } - public string Description { get; set; } - public IDictionary Config { get; set; } - public HomePageData Data { get; set; } -} - -public record HomePageData -{ - public string[] SomeHomePageDataArray { get; set; } -} \ No newline at end of file diff --git a/Epsilon.Host.WebApi/Models/KpiMatrix.cs b/Epsilon.Host.WebApi/Models/KpiMatrix.cs deleted file mode 100644 index 7c73bee2..00000000 --- a/Epsilon.Host.WebApi/Models/KpiMatrix.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Epsilon.Host.WebApi.Interfaces; - -namespace Epsilon.Host.WebApi.Models; - -public record KpiMatrix : IComponent -{ - public int Id { get; set; } - public string Type { get; set; } - public string Title { get; set; } - public string Description { get; set; } - public IDictionary Config { get; set; } - public KpiMatrixData Data { get; set; } -} - -public record KpiMatrixData -{ - public string[] SomeKpiMatrixDataArray { get; set; } -} \ No newline at end of file diff --git a/Epsilon.Host.WebApi/Program.cs b/Epsilon.Host.WebApi/Program.cs index aed648dc..7433ebf4 100644 --- a/Epsilon.Host.WebApi/Program.cs +++ b/Epsilon.Host.WebApi/Program.cs @@ -1,8 +1,8 @@ using Epsilon.Abstractions.Component; +using Epsilon.Abstractions.Service; using Epsilon.Canvas; -using Epsilon.Canvas.Abstractions.Service; -using Epsilon.Canvas.Service; -using Epsilon.Component.Converters; +using Epsilon.Component; +using Epsilon.Service; var builder = WebApplication.CreateBuilder(args); @@ -24,7 +24,13 @@ builder.Services.AddSwaggerGen(); builder.Services.AddCanvas(canvasConfiguration); -builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddComponentFetcher(); +builder.Services.AddComponentFetcher(); +builder.Services.AddComponentFetcher(); var app = builder.Build(); diff --git a/Epsilon.Host.WebApi/Responses/GetDocumentComponentResponse.cs b/Epsilon.Host.WebApi/Responses/GetDocumentComponentResponse.cs deleted file mode 100644 index 091c5f72..00000000 --- a/Epsilon.Host.WebApi/Responses/GetDocumentComponentResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Epsilon.Host.WebApi.Interfaces; - -namespace Epsilon.Host.WebApi.Responses; - -public record GetDocumentComponentResponse -{ - public IComponent Component { get; set; } -} \ No newline at end of file diff --git a/Epsilon.Host.WebApi/Responses/GetDocumentHomePageResponse.cs b/Epsilon.Host.WebApi/Responses/GetDocumentHomePageResponse.cs deleted file mode 100644 index d6473466..00000000 --- a/Epsilon.Host.WebApi/Responses/GetDocumentHomePageResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Epsilon.Host.WebApi.Models; - -namespace Epsilon.Host.WebApi.Responses; - -public record GetDocumentHomePageResponse -{ - public int DocumentId { get; set; } - public HomePage HomePage { get; set; } -} \ No newline at end of file diff --git a/Epsilon.Host.WebApi/Responses/GetDocumentKpiMatrixResponse.cs b/Epsilon.Host.WebApi/Responses/GetDocumentKpiMatrixResponse.cs deleted file mode 100644 index 60e9bc6c..00000000 --- a/Epsilon.Host.WebApi/Responses/GetDocumentKpiMatrixResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Epsilon.Host.WebApi.Models; - -namespace Epsilon.Host.WebApi.Responses; - -public record GetDocumentKpiMatrixResponse -{ - public int DocumentId { get; set; } - public KpiMatrix KpiMatrix { get; set; } -} \ No newline at end of file diff --git a/Epsilon.Host.WebApi/Responses/GetDocumentStructureResponse.cs b/Epsilon.Host.WebApi/Responses/GetDocumentStructureResponse.cs deleted file mode 100644 index ea2d72b4..00000000 --- a/Epsilon.Host.WebApi/Responses/GetDocumentStructureResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Epsilon.Host.WebApi.Responses; - -public record GetDocumentStructureResponse -{ - public int DocumentId { get; set; } - public IEnumerable ComponentIds { get; set; } -} \ No newline at end of file diff --git a/Epsilon.Tests/Epsilon.Tests.csproj b/Epsilon.Tests/Epsilon.Tests.csproj index 807aafbe..e17df0cb 100644 --- a/Epsilon.Tests/Epsilon.Tests.csproj +++ b/Epsilon.Tests/Epsilon.Tests.csproj @@ -4,14 +4,20 @@ net6.0 enable enable - false + latest-All + true + true - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -23,8 +29,8 @@ - - + + diff --git a/Epsilon.Tests/ExportDataPackagerTests.cs b/Epsilon.Tests/ExportDataPackagerTests.cs deleted file mode 100644 index d7879d2e..00000000 --- a/Epsilon.Tests/ExportDataPackagerTests.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Epsilon.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Model; -using Epsilon.Export; -using Moq; -using System.Text.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Epsilon.Tests -{ - public class ExportDataPackagerTests - { - [Fact] - public async Task - GivenListOfModuleOutcomeResultCollection_WhenRequiringOpenLearningOutcomeStructure_ThenOutcomeStructureShouldBeTransformed() - { - // Arrange - var module = new Module(1, "Module 1", 3, new List - { - new ModuleItem(1, "Module 1 Item 1", ModuleItemType.Page, 1), - new ModuleItem(2, "Module 1 Item 2", ModuleItemType.Assignment, 2), - new ModuleItem(3, "Module 1 Item 3", ModuleItemType.Quiz, 3) - }); - - var outcomes = new List - { - new Outcome(1, "Outcome 1", "Outcome 1 EN Short Description NL Long Description"), - new Outcome(2, "Outcome 2", "Outcome 2 EN Short Description NL Long Description"), - }; - - var alignments = new List - { - new Alignment("1", "Alignment 1", new Uri("https://alignment1.com")), - new Alignment("2", "Alignment 2", new Uri("https://alignment2.com")), - }; - - var outcomeResults = new List - { - new OutcomeResult(false, 3, new OutcomeResultLink("user1", "1", "1", "2")), - new OutcomeResult(true, 4.5, new OutcomeResultLink("user2", "2", "2", "3")), - new OutcomeResult(false, null, new OutcomeResultLink("user1", "1", "1", "3")), - }; - - var links = new OutcomeResultCollectionLink(outcomes, alignments); - - var collection = new OutcomeResultCollection(outcomeResults, links); - - var moduleOutcomeResultCollection = new ModuleOutcomeResultCollection(module, collection); - - var data = new List - { - moduleOutcomeResultCollection - }.ToAsyncEnumerable(); - - var expectedData = new ExportData - { - CourseModules = new List - { - new CourseModule - { - Name = "Module 1", - Outcomes = new List - { - new CourseOutcome - { - Name = "Outcome 1", - Description = "Short Description", - Assignments = new List - { - new CourseAssignment - { - Name = "Alignment 2", - Url = "https://alignment2.com/", - Score = "Satisfactory" - } - } - } - } - } - } - }; - - var exportDataPackager = new ExportDataPackager(); - - // Act - var result = await exportDataPackager.GetExportData(data); - - // Assert - Assert.Equal(JsonSerializer.Serialize(expectedData), JsonSerializer.Serialize(result)); - } - } -} \ No newline at end of file diff --git a/Epsilon.Tests/Exporters/WordModuleExporterTests.cs b/Epsilon.Tests/Exporters/WordModuleExporterTests.cs deleted file mode 100644 index db42aba1..00000000 --- a/Epsilon.Tests/Exporters/WordModuleExporterTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Epsilon.Abstractions.Model; -using Epsilon.Export; -using Epsilon.Export.Exporters; -using Microsoft.Extensions.Options; -using DocumentFormat.OpenXml.Packaging; - -namespace Epsilon.Tests.Exporters -{ - public class WordModuleExporterTests - { - [Fact] - public async Task GivenExportData_WhenExportToWord_ThenFileShouldContainExportData() - { - // Arrange - var data = new ExportData - { - CourseModules = new List - { - new CourseModule - { - Name = "Module 1", - Outcomes = new List - { - new CourseOutcome - { - Name = "Outcome 1", - Description = "Short Description", - Assignments = new List - { - new CourseAssignment - {Name = "Assignment 1", Url = "https://assignment1.com/", Score = "Good"}, - new CourseAssignment - { - Name = "Assignment 2", Url = "https://assignment2.com/", Score = "Outstanding" - }, - } - } - } - } - } - }; - - var moduleExporter = new WordModuleExporter(); - - // Act - using var stream = await moduleExporter.Export(data, "word"); - using var document = WordprocessingDocument.Open(stream, false); - - var content = document?.MainDocumentPart?.Document?.Body?.InnerText; - - // Assert - Assert.Contains("Module 1", content); - Assert.Contains("Outcome 1", content); - Assert.Contains("Short Description", content); - Assert.Contains("Assignment 1", content); - Assert.Contains("Assignment 2", content); - } - } -} \ No newline at end of file diff --git a/Epsilon.Tests/Usings.cs b/Epsilon.Tests/Usings.cs index 8c927eb7..e69de29b 100644 --- a/Epsilon.Tests/Usings.cs +++ b/Epsilon.Tests/Usings.cs @@ -1 +0,0 @@ -global using Xunit; \ No newline at end of file diff --git a/Epsilon.sln b/Epsilon.sln index 62b4d37f..655d65c2 100644 --- a/Epsilon.sln +++ b/Epsilon.sln @@ -1,7 +1,5 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Epsilon.Host.Cli", "Epsilon.Host.Cli\Epsilon.Host.Cli.csproj", "{358E6BB1-E2A2-4397-A831-A2194604D532}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Epsilon.Canvas", "Epsilon.Canvas\Epsilon.Canvas.csproj", "{AD6AB75F-D71B-4FDD-8B37-1FB547AAC753}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Epsilon.Canvas.Abstractions", "Epsilon.Canvas.Abstractions\Epsilon.Canvas.Abstractions.csproj", "{A8301F2C-F889-464F-9385-862BFB447435}" @@ -22,10 +20,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {358E6BB1-E2A2-4397-A831-A2194604D532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {358E6BB1-E2A2-4397-A831-A2194604D532}.Debug|Any CPU.Build.0 = Debug|Any CPU - {358E6BB1-E2A2-4397-A831-A2194604D532}.Release|Any CPU.ActiveCfg = Release|Any CPU - {358E6BB1-E2A2-4397-A831-A2194604D532}.Release|Any CPU.Build.0 = Release|Any CPU {AD6AB75F-D71B-4FDD-8B37-1FB547AAC753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD6AB75F-D71B-4FDD-8B37-1FB547AAC753}.Debug|Any CPU.Build.0 = Debug|Any CPU {AD6AB75F-D71B-4FDD-8B37-1FB547AAC753}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -58,5 +52,9 @@ Global EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63F9A5D2-3979-4C37-9A45-C0AF94B7FEE9} + {194C4770-0BCD-4EC2-BC7C-5D672A3B6557}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {194C4770-0BCD-4EC2-BC7C-5D672A3B6557}.Debug|Any CPU.Build.0 = Debug|Any CPU + {194C4770-0BCD-4EC2-BC7C-5D672A3B6557}.Release|Any CPU.ActiveCfg = Release|Any CPU + {194C4770-0BCD-4EC2-BC7C-5D672A3B6557}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Epsilon/Component/CompetenceProfileCompetenceComponentFetcher.cs b/Epsilon/Component/CompetenceProfileCompetenceComponentFetcher.cs new file mode 100644 index 00000000..03ab8745 --- /dev/null +++ b/Epsilon/Component/CompetenceProfileCompetenceComponentFetcher.cs @@ -0,0 +1,167 @@ +using Epsilon.Abstractions.Component; +using Epsilon.Abstractions.Model; +using Epsilon.Canvas.Abstractions.Model; +using Epsilon.Canvas.Abstractions.Model.GraphQl; +using Epsilon.Canvas.Abstractions.Service; +using Microsoft.Extensions.Configuration; + +namespace Epsilon.Component; + +public class CompetenceProfileCompetenceComponentFetcher : CompetenceComponentFetcher +{ + private const string GetAllUserCoursesSubmissionOutcomes = @" + query MyQuery { + allCourses { + submissionsConnection(studentIds: $studentIds) { + nodes { + submissionHistoriesConnection { + nodes { + rubricAssessmentsConnection { + nodes { + assessmentRatings { + criterion { + outcome { + _id + } + masteryPoints + } + points + } + } + } + attempt + submittedAt + } + } + postedAt + } + } + } + "; + + private readonly IConfiguration _configuration; + private readonly IGraphQlHttpService _graphQlService; + private readonly IAccountHttpService _accountHttpService; + + public CompetenceProfileCompetenceComponentFetcher( + IGraphQlHttpService graphQlService, + IAccountHttpService accountHttpService, + IConfiguration configuration + ) + { + _graphQlService = graphQlService; + _accountHttpService = accountHttpService; + _configuration = configuration; + } + + public override async Task Fetch(DateTime startDate, DateTime endDate) + { + var studentId = _configuration["Canvas:StudentId"]; + var outcomesQuery = GetAllUserCoursesSubmissionOutcomes.Replace("$studentIds", $"{studentId}", StringComparison.InvariantCulture); + + var outcomes = await _graphQlService.Query(outcomesQuery); + var terms = await _accountHttpService.GetAllTerms(1); + + var competenceProfile = ConvertToComponent(outcomes, new HboIDomain2018(), terms); + + return competenceProfile; + } + + private static CompetenceProfile ConvertToComponent( + CanvasGraphQlQueryResponse queryResponse, + IHboIDomain domain, + IEnumerable enrollmentTerms + ) + { + var taskResults = new List(); + var professionalResults = new List(); + + if (queryResponse.Data != null) + { + foreach (var course in queryResponse.Data.Courses) + { + foreach (var submissionsConnection in course.SubmissionsConnection.Nodes) + { + var submission = submissionsConnection.SubmissionsHistories.Nodes + .Where(static h => h.RubricAssessments.Nodes.Any()) + .MaxBy(static h => h.Attempt); + + if (submission != null) + { + var rubricAssessments = submission.RubricAssessments.Nodes; + + foreach (var assessmentRating in rubricAssessments.SelectMany(static rubricAssessment => rubricAssessment.AssessmentRatings.Where(static ar => + ar is { Points: not null, Criterion.MasteryPoints: not null, Criterion.Outcome: not null, } && ar.Points >= ar.Criterion.MasteryPoints))) + { + if (FhictConstants.ProfessionalTasks.TryGetValue(assessmentRating.Criterion.Outcome.Id, out var professionalTask)) + { + taskResults.Add( + new ProfessionalTaskResult( + professionalTask.Layer, + professionalTask.Activity, + professionalTask.MasteryLevel, + assessmentRating.Points!.Value, + new DateTime() + ) + ); + } + else if (FhictConstants.ProfessionalSkills.TryGetValue(assessmentRating.Criterion.Outcome.Id, out var professionalSkill)) + { + professionalResults.Add( + new ProfessionalSkillResult( + professionalSkill.Skill, + professionalSkill.MasteryLevel, + assessmentRating.Points!.Value, + new DateTime() + ) + ); + } + } + } + } + } + } + + var filteredTerms = enrollmentTerms + .Where(static term => term is { StartAt: not null, EndAt: not null, }) + .Where(term => taskResults.Any(taskOutcome => + taskOutcome.AssessedAt >= term.StartAt && taskOutcome.AssessedAt <= term.EndAt) + || professionalResults.Any(skillOutcome => + skillOutcome.AssessedAt > term.StartAt && skillOutcome.AssessedAt < term.EndAt)) + .Distinct() + .OrderBy(static term => term.StartAt); + + return new CompetenceProfile( + domain, + taskResults, + professionalResults, + filteredTerms, + GetDecayingAverageTasks(domain, taskResults), + GetDecayingAverageSkills(domain, professionalResults) + ); + } + + private static IEnumerable GetDecayingAverageTasks(IHboIDomain domain, IEnumerable taskResults) + { + return domain.ArchitectureLayers.Select(layer => new DecayingAveragePerLayer(layer.Id, + domain.Activities.Select(activity => + { + var decayingAverage = taskResults + .Where(task => task.ArchitectureLayer == layer.Id && task.Activity == activity.Id) + .Aggregate(0, static (current, outcome) => current * 0.35 + outcome.Grade * 0.65); + + return new DecayingAveragePerActivity(activity.Id, decayingAverage); + }))); + } + + private static IEnumerable GetDecayingAverageSkills(IHboIDomain domain, IEnumerable skillResults) + { + return domain.ProfessionalSkills.Select(skill => + { + var decayingAverage = skillResults.Where(outcome => outcome.Skill == skill.Id) + .Aggregate(0, static (current, outcome) => current * 0.35 + outcome.Grade * 0.65); + + return new DecayingAveragePerSkill(skill.Id, decayingAverage); + }); + } +} \ No newline at end of file diff --git a/Epsilon/Component/ComponentServiceCollectionExtensions.cs b/Epsilon/Component/ComponentServiceCollectionExtensions.cs new file mode 100644 index 00000000..0a867056 --- /dev/null +++ b/Epsilon/Component/ComponentServiceCollectionExtensions.cs @@ -0,0 +1,17 @@ +using Epsilon.Abstractions.Component; +using Microsoft.Extensions.DependencyInjection; + +namespace Epsilon.Component; + +public static class ComponentServiceCollectionExtensions +{ + public static IServiceCollection AddComponentFetcher(this IServiceCollection services) + where TFetcher : class, ICompetenceComponentFetcher, ICompetenceComponentFetcher + where TComponent : ICompetenceComponent + { + services.AddScoped(); + services.AddScoped, TFetcher>(); + + return services; + } +} \ No newline at end of file diff --git a/Epsilon/Component/Converters/CompetenceProfileConverter.cs b/Epsilon/Component/Converters/CompetenceProfileConverter.cs deleted file mode 100644 index 58da5329..00000000 --- a/Epsilon/Component/Converters/CompetenceProfileConverter.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Epsilon.Abstractions.Component; -using Epsilon.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Model.GraphQl; -using Epsilon.Canvas.Abstractions.QueryResponse; - -namespace Epsilon.Component.Converters; - -public class CompetenceProfileConverter : ICompetenceProfileConverter -{ - public IEnumerable GetDecayingAverageTasks(IHboIDomain domain, IEnumerable taskResults) - { - return domain.ArchitectureLayers.Select(layer => new DecayingAveragePerLayer(layer.Id, - domain.Activities.Select(activity => - { - var decayingAverage = taskResults - .Where(task => task.ArchitectureLayer == layer.Id && task.Activity == activity.Id) - .Aggregate(0, - (current, outcome) => current * 0.35 + outcome.Grade * 0.65); - - return new DecayingAveragePerActivity(activity.Id, decayingAverage); - }))); - } - - public IEnumerable GetDecayingAverageSkills(IHboIDomain domain, IEnumerable skillResults) - { - return domain.ProfessionalSkills.Select(skill => - { - var decayingAverage = skillResults.Where(outcome => outcome.Skill == skill.Id) - .Aggregate(0, - (current, outcome) => current * 0.35 + outcome.Grade * 0.65); - - return new DecayingAveragePerSkill(skill.Id, decayingAverage); - }); - } - - public CompetenceProfile ConvertFrom( - GetAllUserCoursesSubmissionOutcomes getAllUserCoursesSubmissionOutcomes, - IHboIDomain domain, - IEnumerable enrollmentTerms) - { - var taskResults = new List(); - var professionalResults = new List(); - - foreach (var course in getAllUserCoursesSubmissionOutcomes.Data.Courses) - { - foreach (var submissionsConnection in course.SubmissionsConnection.Nodes.Where(static s => s.PostedAt != null)) - { - var submission = submissionsConnection.SubmissionsHistories.Nodes - .Where(static h => h.RubricAssessments.Nodes.Any()) - .MaxBy(static h => h.Attempt); - - if (submission != null) - { - var rubricAssessments = submission.RubricAssessments.Nodes; - - foreach (var assessmentRating in rubricAssessments.SelectMany(static rubricAssessment => rubricAssessment.AssessmentRatings.Where(static ar => ar is { Points: not null, Criterion.MasteryPoints: not null, Criterion.Outcome: not null } && ar.Points >= ar.Criterion.MasteryPoints))) - { - if (FhictConstants.ProfessionalTasks.TryGetValue(assessmentRating.Criterion.Outcome.Id, out var professionalTask)) - { - taskResults.Add( - new ProfessionalTaskResult( - professionalTask.Layer, - professionalTask.Activity, - professionalTask.MasteryLevel, - assessmentRating.Points!.Value, - submissionsConnection.PostedAt!.Value - ) - ); - } - else if (FhictConstants.ProfessionalSkills.TryGetValue(assessmentRating.Criterion.Outcome.Id, out var professionalSkill)) - { - professionalResults.Add( - new ProfessionalSkillResult( - professionalSkill.Skill, - professionalSkill.MasteryLevel, - assessmentRating.Points!.Value, - submissionsConnection.PostedAt!.Value - ) - ); - } - } - } - } - } - - var filteredTerms = enrollmentTerms - .Where(static term => term is { StartAt: not null, EndAt: not null }) - .Where(term => taskResults.Any(taskOutcome => - taskOutcome.AssessedAt >= term.StartAt && taskOutcome.AssessedAt <= term.EndAt) - || professionalResults.Any(skillOutcome => - skillOutcome.AssessedAt > term.StartAt && skillOutcome.AssessedAt < term.EndAt)) - .Distinct() - .OrderBy(static term => term.StartAt); - - return new CompetenceProfile( - domain, - taskResults, - professionalResults, - filteredTerms, - GetDecayingAverageTasks(domain, taskResults), - GetDecayingAverageSkills(domain, professionalResults) - ); - } -} \ No newline at end of file diff --git a/Epsilon/Component/KpiMatrixComponentFetcher.cs b/Epsilon/Component/KpiMatrixComponentFetcher.cs new file mode 100644 index 00000000..22161df9 --- /dev/null +++ b/Epsilon/Component/KpiMatrixComponentFetcher.cs @@ -0,0 +1,137 @@ +using Epsilon.Abstractions.Component; +using Epsilon.Canvas.Abstractions.Model; +using Epsilon.Canvas.Abstractions.Model.GraphQl; +using Epsilon.Canvas.Abstractions.Service; +using Microsoft.Extensions.Configuration; + +namespace Epsilon.Component; + +public class KpiMatrixComponentFetcher : CompetenceComponentFetcher +{ + private const string GetUserKpiMatrixOutcomes = @" + query GetUserKpiMatrixOutcomes { + allCourses { + submissionsConnection(studentIds: $studentIds) { + nodes { + submissionHistoriesConnection { + nodes { + rubricAssessmentsConnection { + nodes { + assessmentRatings { + criterion { + outcome { + _id + title + } + masteryPoints + } + points + } + } + } + attempt + submittedAt + assignment { + name + rubric { + criteria { + outcome { + title + _id + masteryPoints + } + } + } + } + } + } + postedAt + } + } + } + } + "; + + private readonly IConfiguration _configuration; + private readonly IGraphQlHttpService _graphQlService; + + public KpiMatrixComponentFetcher( + IGraphQlHttpService graphQlService, + IConfiguration configuration + ) + { + _graphQlService = graphQlService; + _configuration = configuration; + } + + public override async Task Fetch(DateTime startDate, DateTime endDate) + { + var studentId = _configuration["Canvas:StudentId"]; + var outcomesQuery = GetUserKpiMatrixOutcomes.Replace("$studentIds", $"{studentId}", StringComparison.InvariantCultureIgnoreCase); + var outcomes = await _graphQlService.Query(outcomesQuery); + + + var assignments = new List(); + foreach (var course in outcomes.Data!.Courses!) + { + foreach (var submissionsConnection in course.SubmissionsConnection!.Nodes) + { + var submission = submissionsConnection.SubmissionsHistories.Nodes + .Where(sub => sub.SubmittedAt > startDate && sub.SubmittedAt < endDate) + .MaxBy(static h => h.Attempt); + + if (submission is + { + Assignment.Rubric: not null, + RubricAssessments.Nodes: not null, + }) + { + var rubricCriteria = submission.Assignment.Rubric.Criteria?.ToArray(); + var kpiMatrixOutcomes = new List(); + + if (rubricCriteria != null) + { + foreach (var outcome in rubricCriteria.Select(static criteria => criteria.Outcome)) + { + //Validate that outcome is a HboI KPI + if (outcome != null + && (FhictConstants.ProfessionalTasks.ContainsKey(outcome.Id) || FhictConstants.ProfessionalSkills.ContainsKey(outcome.Id)) + && rubricCriteria.Any()) + { + var assessmentRatings = submission.RubricAssessments.Nodes.FirstOrDefault()?.AssessmentRatings; + var gradeStatus = GetGradeStatus(assessmentRatings?.FirstOrDefault(ar => ar?.Criterion?.Outcome?.Id == outcome.Id)); + + kpiMatrixOutcomes.Add(new KpiMatrixOutcome(outcome.Id, outcome.Title, KpiMatrixCollection.DefaultGradeStatus[gradeStatus])); + } + } + } + + if (kpiMatrixOutcomes.Count > 0 && submission.Assignment.Name != null) + { + var assignment = new KpiMatrixAssignment(submission.Assignment.Name, kpiMatrixOutcomes); + assignments.Add(assignment); + } + } + } + } + + return new KpiMatrixCollection(assignments, KpiMatrixCollection.DefaultGradeStatus); + } + + private static OutcomeGradeStatus GetGradeStatus(AssessmentRating? rating) + { + if (rating != null) + { + if (rating.Points != null) + { + return rating.IsMastery + ? OutcomeGradeStatus.Mastered + : OutcomeGradeStatus.NotMastered; + } + + return OutcomeGradeStatus.NotGraded; + } + + return OutcomeGradeStatus.NotAssessed; + } +} \ No newline at end of file diff --git a/Epsilon/Component/PersonaPageComponentFetcher.cs b/Epsilon/Component/PersonaPageComponentFetcher.cs new file mode 100644 index 00000000..889fddef --- /dev/null +++ b/Epsilon/Component/PersonaPageComponentFetcher.cs @@ -0,0 +1,66 @@ +using Epsilon.Abstractions.Component; +using Epsilon.Canvas; +using Epsilon.Canvas.Abstractions.Service; +using HtmlAgilityPack; +using Microsoft.Extensions.Options; + +namespace Epsilon.Component; + +public class PersonaPageComponentFetcher : CompetenceComponentFetcher +{ + private readonly IPageHttpService _pageHttpService; + private readonly IFileHttpService _fileHttpService; + private readonly CanvasSettings _canvasSettings; + + public PersonaPageComponentFetcher( + IPageHttpService pageHttpService, + IFileHttpService fileHttpService, + IOptions canvasSettings + ) + { + _pageHttpService = pageHttpService; + _fileHttpService = fileHttpService; + _canvasSettings = canvasSettings.Value; + } + + public override async Task Fetch(DateTime startDate, DateTime endDate) + { + var courseId = _canvasSettings.CourseId; + var personaHtml = await _pageHttpService.GetPageByName(courseId, "front_page"); + + var updatedPersonaHtml = await GetPersonaHtmlDocument(personaHtml); + + var personaPage = new PersonaPage(updatedPersonaHtml.Text); + + return personaPage; + } + + private async Task GetPersonaHtmlDocument(string htmlString) + { + var htmlDoc = new HtmlDocument(); + htmlDoc.LoadHtml(htmlString); + if (htmlDoc.DocumentNode.SelectNodes("//img") == null) + { + return htmlDoc; + } + + foreach (var node in htmlDoc.DocumentNode.SelectNodes("//img")) + { + var imageSrc = node + .SelectNodes("//img") + .First() + .Attributes["src"].Value; + + if (imageSrc != null) + { + var imageBytes = await _fileHttpService.GetFileByteArray(new Uri(imageSrc)); + var imageBase64 = Convert.ToBase64String(imageBytes.ToArray()); + + node.SetAttributeValue("src", $"data:image/jpeg;base64,{imageBase64}"); + } + } + + return htmlDoc; + } + +} \ No newline at end of file diff --git a/Epsilon/Constants.cs b/Epsilon/Constants.cs deleted file mode 100644 index 9fdd106f..00000000 --- a/Epsilon/Constants.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Epsilon; - -public static class Constants -{ - public const string ProjectName = "Epsilon"; - public static readonly Uri RepositoryUri = new("https://github.com/Typiqally/epsilon"); -} \ No newline at end of file diff --git a/Epsilon/Epsilon.csproj b/Epsilon/Epsilon.csproj index 120f0714..ed42d439 100644 --- a/Epsilon/Epsilon.csproj +++ b/Epsilon/Epsilon.csproj @@ -4,14 +4,21 @@ net6.0 enable enable + latest-All + true + true - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Epsilon/Export/Exceptions/NoExportersFoundException.cs b/Epsilon/Export/Exceptions/NoExportersFoundException.cs deleted file mode 100644 index 2cf7b1db..00000000 --- a/Epsilon/Export/Exceptions/NoExportersFoundException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.Serialization; - -namespace Epsilon.Export.Exceptions; - -[Serializable] -public class NoExportersFoundException : Exception -{ - public NoExportersFoundException() - { - } - - protected NoExportersFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public NoExportersFoundException(IEnumerable formats) - : base($"No exporters could be found with the given formats {string.Join(",", formats)}") - { - } -} \ No newline at end of file diff --git a/Epsilon/Export/ExportDataPackager.cs b/Epsilon/Export/ExportDataPackager.cs deleted file mode 100644 index 9aaca7b5..00000000 --- a/Epsilon/Export/ExportDataPackager.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Diagnostics; -using Epsilon.Abstractions.Export; -using Epsilon.Abstractions.Model; -using Epsilon.Canvas; -using Epsilon.Canvas.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Service; -using Microsoft.Extensions.Options; - -namespace Epsilon.Export; - -public class ExportDataPackager : IExportDataPackager -{ - private readonly IPageHttpService _pageService; - private readonly CanvasSettings _canvasSettings; - - public ExportDataPackager(IPageHttpService pageService, - IOptions canvasSettings) - { - _pageService = pageService; - _canvasSettings = canvasSettings.Value; - } - - public ExportDataPackager() - { - throw new NotImplementedException(); - } - - public async Task GetExportData(IAsyncEnumerable data) - { - var courseId = _canvasSettings.CourseId; - var personaHtml = await _pageService.GetPageByName(courseId, "front_page"); - - var output = new List(); - - await foreach (var item in data.Where(m => m.Collection.OutcomeResults.Any())) - { - var module = new CourseModule {Name = item.Module.Name}; - var links = item.Collection.Links; - - Debug.Assert(links != null, nameof(links) + " != null"); - - var alignments = links.AlignmentsDictionary; - var outcomes = links.OutcomesDictionary; - - var moduleOutcomes = new List(); - - foreach (var (outcomeId, outcome) in outcomes) - { - var assignmentIds = item.Collection.OutcomeResults - .Where(o => o.Link.Outcome == outcomeId && o.Grade() != null) - .Select(static o => o.Link.Assignment).ToArray(); - - if (assignmentIds.Any()) - { - var assignments = assignmentIds - .Select(assignmentId => new CourseAssignment - { - Name = alignments[assignmentId!].Name, - Url = alignments[assignmentId!].Url.ToString(), - Score = item.Collection.OutcomeResults - .First(o => o.Link.Outcome == outcomeId && o.Link.Assignment == assignmentId) - .Grade() ?? "N/A", - }) - .ToList(); - - moduleOutcomes.Add(new CourseOutcome - { - Name = outcome.Title, - Description = outcome.ShortDescription(), - Assignments = assignments!, - }); - } - } - - module.Outcomes = moduleOutcomes; - - output.Add(module); - } - - return new ExportData {CourseModules = output, PersonaHtml = personaHtml}; - } -} \ No newline at end of file diff --git a/Epsilon/Export/ExportOptions.cs b/Epsilon/Export/ExportOptions.cs deleted file mode 100644 index 2d9f5064..00000000 --- a/Epsilon/Export/ExportOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Epsilon.Export; - -public class ExportOptions -{ - public string OutputName { get; set; } = $"{Constants.ProjectName}-Export-{{DateTime}}"; - - public string Formats { get; set; } = "console"; - - public string? Modules { get; set; } - - public string FormattedOutputName => OutputName - .Replace("{DateTime}", DateTime.Now.ToString("ddMMyyyyHHmmss")); -} \ No newline at end of file diff --git a/Epsilon/Export/Exporters/ConsoleModuleExporter.cs b/Epsilon/Export/Exporters/ConsoleModuleExporter.cs deleted file mode 100644 index 1a9492c3..00000000 --- a/Epsilon/Export/Exporters/ConsoleModuleExporter.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Epsilon.Abstractions.Export; -using Epsilon.Abstractions.Model; -using Microsoft.Extensions.Logging; - -namespace Epsilon.Export.Exporters; - -public class ConsoleModuleExporter : ICanvasModuleExporter -{ - private readonly ILogger _logger; - - public ConsoleModuleExporter(ILogger logger) - { - _logger = logger; - } - - public IEnumerable Formats { get; } = new[] {"console", "logs", "txt"}; - - public string FileExtension => "txt"; - - - public async Task Export(ExportData data, string format) - { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - - foreach (var module in data.CourseModules) - { - await WriteLineAndLog(writer, "--------------------------------"); - await WriteLineAndLog(writer, $"Module: {module.Name}"); - - foreach (var outcome in module.Outcomes) - { - await WriteLineAndLog(writer, ""); - await WriteLineAndLog(writer, $"KPI: {outcome.Name}"); - - foreach (var assignment in outcome.Assignments) - { - await WriteLineAndLog(writer, $"- Assignment: {assignment.Name}"); - await WriteLineAndLog(writer, $" Score: {assignment.Score}"); - } - } - } - - // await WriteLineAndLog(writer, $"Decaying Average: {}"); - - await writer.FlushAsync(); - - return stream; - } - - private async Task WriteLineAndLog(TextWriter writer, string line) - { - await writer.WriteLineAsync(line); - _logger.LogInformation(line); - } -} \ No newline at end of file diff --git a/Epsilon/Export/Exporters/CsvModuleExporter.cs b/Epsilon/Export/Exporters/CsvModuleExporter.cs deleted file mode 100644 index d8b6055c..00000000 --- a/Epsilon/Export/Exporters/CsvModuleExporter.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Data; -using Epsilon.Abstractions.Export; -using Epsilon.Abstractions.Model; - -namespace Epsilon.Export.Exporters; - -public class CsvModuleExporter : ICanvasModuleExporter -{ - public IEnumerable Formats { get; } = new[] {"csv"}; - - public string FileExtension => "csv"; - - public async Task Export(ExportData data, string format) - { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - - var dt = CreateDataTable(data.CourseModules); - WriteHeader(writer, dt); - WriteRows(writer, dt); - - await writer.FlushAsync(); - - return stream; - } - - private static DataTable CreateDataTable(IEnumerable data) - { - var dataTable = new DataTable(); - - dataTable.Columns.Add("Module", typeof(string)); - dataTable.Columns.Add("KPI", typeof(string)); - dataTable.Columns.Add("Assignment", typeof(string)); - dataTable.Columns.Add("Score", typeof(string)); - - foreach (var module in data) - { - foreach (var kpi in module.Outcomes) - { - foreach (var assignment in kpi.Assignments) - { - dataTable.Rows.Add(module.Name, kpi.Name, assignment.Name, assignment.Score); - } - } - } - - return dataTable; - } - - private static void WriteHeader(TextWriter writer, DataTable dt) - { - for (var i = 0; i < dt.Columns.Count; i++) - { - writer.Write(dt.Columns[i]); - if (i < dt.Columns.Count - 1) - { - writer.Write(";"); - } - } - - writer.Write(writer.NewLine); - } - - private static void WriteRows(TextWriter writer, DataTable dt) - { - foreach (DataRow dr in dt.Rows) - { - foreach (DataColumn dtColumn in dt.Columns) - { - var value = dr[dtColumn.Ordinal].ToString(); - if (value != null) - { - if (value.Contains(';')) - { - value = $"\"{value}\""; - writer.Write(value); - } - else - { - writer.Write(value); - } - } - - if (dtColumn.Ordinal < dt.Columns.Count - 1) - { - writer.Write(";"); - } - } - - writer.Write(writer.NewLine); - } - } -} \ No newline at end of file diff --git a/Epsilon/Export/Exporters/ExcelModuleExporter.cs b/Epsilon/Export/Exporters/ExcelModuleExporter.cs deleted file mode 100644 index f5d86da8..00000000 --- a/Epsilon/Export/Exporters/ExcelModuleExporter.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.Text; -using DocumentFormat.OpenXml; -using DocumentFormat.OpenXml.Packaging; -using DocumentFormat.OpenXml.Spreadsheet; -using Epsilon.Abstractions.Export; -using Epsilon.Abstractions.Model; - -namespace Epsilon.Export.Exporters; - -public class ExcelModuleExporter : ICanvasModuleExporter -{ - public IEnumerable Formats { get; } = new[] {"xls", "xlsx", "excel"}; - - public string FileExtension => "xlsx"; - - public async Task Export(ExportData data, string format) - { - var stream = new MemoryStream(); - using var spreadsheetDocument = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook); - var workbookPart = spreadsheetDocument.AddWorkbookPart(); - workbookPart.Workbook = new Workbook(); - - // Add Sheets to the Workbook. - spreadsheetDocument.WorkbookPart!.Workbook.AppendChild(new Sheets()); - - var cellValueBuilder = new StringBuilder(); - var cellValueOutComeResultsBuilder = new StringBuilder(); - - foreach (var module in data.CourseModules) - { - var worksheetPart = CreateWorksheet(module, workbookPart); - - InsertCellsInWorksheet( - worksheetPart, - 1, - CreateTextCell("KPI", "A", 1), - CreateTextCell("Assignment", "B", 1), - CreateTextCell("Grade", "C", 1) - ); - - uint count = 2; - foreach (var outcome in module.Outcomes) - { - foreach (var assignment in outcome.Assignments) - { - cellValueBuilder.AppendLine($"{assignment.Name} {assignment.Url}"); - cellValueOutComeResultsBuilder.AppendLine(assignment.Score); - } - - InsertCellsInWorksheet( - worksheetPart, - count, - CreateTextCell($"{outcome.Name} {outcome.Description}", "A", count), - CreateTextCell(cellValueBuilder.ToString(), "B", count), - CreateTextCell(cellValueOutComeResultsBuilder.ToString(), "C", count) - ); - - cellValueBuilder.Clear(); - cellValueOutComeResultsBuilder.Clear(); - - count++; - } - } - - return stream; - } - - // Given a WorkbookPart, inserts a new worksheet. - private static WorksheetPart CreateWorksheet(CourseModule module, WorkbookPart workbookPart) - { - var worksheetPart = workbookPart.AddNewPart(); - worksheetPart.Worksheet = new Worksheet(new SheetData()); - worksheetPart.Worksheet.Save(); - - var sheets = workbookPart.Workbook.GetFirstChild()!; - var relationshipId = workbookPart.GetIdOfPart(worksheetPart); - - uint sheetId = 1; - if (sheets.Elements().Any()) - { - sheetId = sheets.Elements().Select(static s => s.SheetId!.Value).Max() + 1; - } - - var sheet = new Sheet {Id = relationshipId, SheetId = sheetId, Name = module.Name}; - sheets.Append(sheet); - - workbookPart.Workbook.Save(); - - return worksheetPart; - } - - private static void InsertCellsInWorksheet(WorksheetPart worksheetPart, uint rowIndex, params Cell[] cells) - { - var worksheet = worksheetPart.Worksheet; - var sheetData = worksheet.GetFirstChild()!; - - var row = sheetData.Elements().FirstOrDefault(r => r.RowIndex! == rowIndex); - if (row == null) - { - row = new Row {RowIndex = rowIndex}; - sheetData.Append(row); - } - - row.Append(cells); - worksheet.Save(); - } - - private static Cell CreateTextCell(string value, string columnName, uint rowIndex) => new() - { - CellReference = columnName + rowIndex, - CellValue = new CellValue(value), - DataType = CellValues.String, - }; -} \ No newline at end of file diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs deleted file mode 100644 index 036c627a..00000000 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System.Text; -using DocumentFormat.OpenXml; -using DocumentFormat.OpenXml.Packaging; -using DocumentFormat.OpenXml.Wordprocessing; -using Epsilon.Abstractions.Export; -using Epsilon.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Service; -using HtmlAgilityPack; - -namespace Epsilon.Export.Exporters; - -public class WordModuleExporter : ICanvasModuleExporter -{ - private static readonly TableBorders s_defaultBorders = new( - new TopBorder {Val = new EnumValue(BorderValues.Single), Size = 3}, - new BottomBorder {Val = new EnumValue(BorderValues.Single), Size = 3}, - new LeftBorder {Val = new EnumValue(BorderValues.Single), Size = 3}, - new RightBorder {Val = new EnumValue(BorderValues.Single), Size = 3}, - new InsideHorizontalBorder {Val = new EnumValue(BorderValues.Single), Size = 6}, - new InsideVerticalBorder {Val = new EnumValue(BorderValues.Single), Size = 6} - ); - - private static readonly TableProperties s_defaultTableProperties = new(s_defaultBorders); - - private static readonly TableRow s_defaultHeader = new( - CreateTextCell("KPI's"), - CreateTextCell("Assignments"), - CreateTextCell("Score") - ); - - private readonly IFileHttpService _fileService; - public IEnumerable Formats { get; } = new[] {"word", "docx"}; - public string FileExtension => "docx"; - - public WordModuleExporter(IFileHttpService fileService) - { - _fileService = fileService; - } - - public WordModuleExporter() - { - throw new NotImplementedException(); - } - - public async Task Export(ExportData data, string format) - { - var stream = new MemoryStream(); - using var document = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document); - - document.AddMainDocumentPart(); - document.MainDocumentPart!.Document = new Document(new Body()); - - var body = document.MainDocumentPart.Document.Body; - var cellValueBuilder = new StringBuilder(); - var cellValueOutComeResultsBuilder = new StringBuilder(); - - var altChunkId = "HomePage"; - - var personaHTML = new HtmlDocument(); - personaHTML.LoadHtml(data.PersonaHtml); - - - using var ms = new MemoryStream(new UTF8Encoding(true).GetPreamble() - .Concat(Encoding.UTF8.GetBytes($"{personaHTML.Text}")).ToArray()); - - var formatImportPart = - document.MainDocumentPart.AddAlternativeFormatImportPart( - AlternativeFormatImportPartType.Html, altChunkId); - - formatImportPart.FeedData(ms); - AltChunk altChunk = new AltChunk(); - altChunk.Id = altChunkId; - - body?.Append(altChunk); - ms.DisposeAsync(); - body?.Append(new Paragraph(new Run(new Break() {Type = BreakValues.Page}))); - - foreach (var module in data.CourseModules) - { - body?.Append(CreateText(module.Name)); - - var table = new Table(); - - table.AppendChild(s_defaultTableProperties.CloneNode(true)); - table.Append(s_defaultHeader.CloneNode(true)); - - var rows = module.Outcomes.Select(kpi => - { - foreach (var assignment in kpi.Assignments) - { - cellValueBuilder.AppendLine($"{assignment.Name} {assignment.Url}"); - cellValueOutComeResultsBuilder.AppendLine(assignment.Score); - } - - var row = new TableRow( - CreateTextCell($"{kpi.Name} {kpi.Description}"), - CreateTextCell(cellValueBuilder.ToString()), - CreateTextCell(cellValueOutComeResultsBuilder.ToString()) - ); - - cellValueBuilder.Clear(); - cellValueOutComeResultsBuilder.Clear(); - - return row; - }); - - table.Append(rows); - - body?.Append(table); - } - - document.Save(); - document.Close(); - - return stream; - } - - private static Paragraph CreateText(string text) => new(new Run(new Text(text))); - - private static TableCell CreateTextCell(string text) => new( - CreateText(text), - new TableCellProperties(new TableCellWidth {Type = TableWidthUnitValues.Auto}) - ); - - private async Task ReplaceImageSrcWithBase64String(HtmlDocument htmlDoc) - { - foreach (var node in htmlDoc.DocumentNode.SelectNodes("//img")) - { - var imageSrc = node - .SelectNodes("//img") - .First() - .Attributes["src"].Value; - - if (imageSrc == null) - throw new ArgumentNullException(nameof(imageSrc)); - - var imageBytes = await _fileService.GetFileByteArray(imageSrc); - var imageBase64 = Convert.ToBase64String(imageBytes.ToArray()); - - node.SetAttributeValue("src", $"data:image/jpeg;base64,{imageBase64}"); - } - } -} \ No newline at end of file diff --git a/Epsilon/Export/ModuleExporterCollection.cs b/Epsilon/Export/ModuleExporterCollection.cs deleted file mode 100644 index d494ba4c..00000000 --- a/Epsilon/Export/ModuleExporterCollection.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Epsilon.Abstractions.Export; -using Epsilon.Export.Exceptions; - -namespace Epsilon.Export; - -public class ModuleExporterCollection : IModuleExporterCollection -{ - private readonly IEnumerable _exporters; - - public ModuleExporterCollection(IEnumerable exporters) - { - _exporters = exporters; - } - - public IEnumerable Formats() - { - return _exporters.SelectMany(static x => x.Formats); - } - - public IDictionary DetermineExporters(IEnumerable formats) - { - var formatsArray = formats.ToArray(); // To prevent multiple enumeration - var foundExporters = new Dictionary(); - - foreach (var exporter in _exporters) - { - foreach (var format in formatsArray.Where(f => exporter.Formats.Contains(f.ToLower()))) - { - foundExporters.Add(format, exporter); - } - } - - if (!foundExporters.Any()) - { - throw new NoExportersFoundException(formatsArray); - } - - return foundExporters; - } -} \ No newline at end of file diff --git a/Epsilon/Extensions/CoreServiceCollectionExtensions.cs b/Epsilon/Extensions/CoreServiceCollectionExtensions.cs deleted file mode 100644 index cd4898a6..00000000 --- a/Epsilon/Extensions/CoreServiceCollectionExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Epsilon.Abstractions.Export; -using Epsilon.Canvas; -using Epsilon.Canvas.Abstractions.Service; -using Epsilon.Canvas.Service; -using Epsilon.Export; -using Epsilon.Export.Exporters; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Epsilon.Extensions; - -public static class CoreServiceCollectionExtensions -{ - public static IServiceCollection AddCore(this IServiceCollection services, IConfiguration config) - { - services.AddCanvas(config.GetSection("Canvas")); - services.AddExport(config); - - return services; - } - - private static IServiceCollection AddExport(this IServiceCollection services, IConfiguration config) - { - services.Configure(config); - - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddScoped(); - services.AddScoped(); - - return services; - } -} \ No newline at end of file diff --git a/Epsilon/Service/CompetenceComponentService.cs b/Epsilon/Service/CompetenceComponentService.cs new file mode 100644 index 00000000..46ac0143 --- /dev/null +++ b/Epsilon/Service/CompetenceComponentService.cs @@ -0,0 +1,57 @@ +using System.Reflection; +using Epsilon.Abstractions.Component; +using Epsilon.Abstractions.Service; + +namespace Epsilon.Service; + +public class CompetenceComponentService : ICompetenceComponentService +{ + private readonly IEnumerable _componentFetchers; + + public CompetenceComponentService(IEnumerable componentFetchers) + { + _componentFetchers = componentFetchers; + } + + public async IAsyncEnumerable GetComponents(DateTime startDate, DateTime endDate) + { + foreach (var componentFetcher in _componentFetchers) + { + yield return await componentFetcher.FetchUnknown(startDate, endDate); + } + } + + public async IAsyncEnumerable GetComponents(DateTime startDate, DateTime endDate) where TComponent : ICompetenceComponent + { + await foreach (var component in GetComponents(startDate, endDate)) + { + if (component is TComponent componentOfT) + { + yield return componentOfT; + } + } + } + + public async Task GetComponent(string name, DateTime startDate, DateTime endDate) + { + var fetcher = _componentFetchers.SingleOrDefault(f => + { + var componentType = f.GetType().BaseType?.GetGenericArguments()[0]; + if (componentType != null) + { + var componentNameAttribute = componentType.GetCustomAttribute(false); + return componentNameAttribute?.Name == name; + } + + return false; + }); + return fetcher == null + ? null + : await fetcher.FetchUnknown(startDate, endDate); + } + + public async Task GetComponent(string name, DateTime startDate, DateTime endDate) where TComponent : class, ICompetenceComponent + { + return await GetComponent(name, startDate, endDate) as TComponent; + } +} \ No newline at end of file diff --git a/Epsilon/Service/CompetenceDocumentService.cs b/Epsilon/Service/CompetenceDocumentService.cs new file mode 100644 index 00000000..ea67e5b8 --- /dev/null +++ b/Epsilon/Service/CompetenceDocumentService.cs @@ -0,0 +1,41 @@ +using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; +using Epsilon.Abstractions.Component; +using Epsilon.Abstractions.Service; + +namespace Epsilon.Service; + +public class CompetenceDocumentService : ICompetenceDocumentService +{ + private readonly ICompetenceComponentService _competenceComponentService; + + public CompetenceDocumentService(ICompetenceComponentService competenceComponentService) + { + _competenceComponentService = competenceComponentService; + } + + public async Task WriteDocument(Stream stream, DateTime startDate, DateTime endDate) + { + var startPosition = stream.Position; + + var components = await _competenceComponentService.GetComponents(startDate, endDate).ToListAsync(); + using var document = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document); + + document.AddMainDocumentPart(); + document.MainDocumentPart!.Document = new Document(); + + foreach (var competenceWordComponent in components) + { + competenceWordComponent.AddToWordDocument(document.MainDocumentPart); + } + + document.Save(); + document.Close(); + + // Reset stream position to start position + stream.Position = startPosition; + + return stream; + } +} \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 00000000..ba13a669 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "6.0.406", + "rollForward": "latestFeature" + } +} \ No newline at end of file